add spinal cocotb code
diff --git a/README.md b/README.md index 4acbc90..4602c82 100644 --- a/README.md +++ b/README.md
@@ -49,26 +49,26 @@ ## Code Structure -To compatible with Caravel user project, the Spinal HDL, cocotb code is seperated from Caravel user project code. +To compatible with Caravel user project, the Spinal HDL, cocotb code is separated from Caravel user project code. ### Spinal HDL and cocotb Code The Spinal HDL and cocotb code is located in [spinal-cocotb](./spinal-cocotb). Some important code files are: * [DmaController.scala](./spinal-cocotb/SpinalNet/src/main/scala/dma/DmaController.scala) is the DMA controller implemented in Spinal HDL; * [DmaMem.scala](./spinal-cocotb/SpinalNet/src/main/scala/dma/DmaMem.scala) is the top module that connect the DMA controller and the SDRAM controller; -* [DmaControllerSim.scala](./spinal-cocotb/SpinalNet/src/main/scala/sdram/DmaControllerSim.scala) is the DMA controller simulation implemented in Spinal HDL; +* [DmaControllerSim.scala](./spinal-cocotb/SpinalNet/src/main/scala/dma/DmaControllerSim.scala) is the DMA controller simulation implemented in Spinal HDL; * [SdramController.scala](./spinal-cocotb/SpinalNet/src/main/scala/sdram/SdramController.scala) is the SDRAM controller implemented in Spinal HDL; -* [SdramControllerTest.py](./spinal-cocotb/SpinalNet/test/src/python/sdram/SdramControllerTest.py) is the SDRAM controller test bench implemented in cocotb. +* [SdramControllerTest.py](./spinal-cocotb/SpinalNet/test/src/python/sdram_controller/SdramControllerTest.py) is the SDRAM controller test bench implemented in cocotb. To build and test the Spinal HDL code, there are two shell scripts: * [setup.sh](./spinal-cocotb/setup.sh) is the script to install the dependent libraries; * [run.sh](./spinal-cocotb/run.sh) is the script to build and run the Spinal HDL and cocotb code. -The `run.sh` script will generate the Verilog code, [DmaMem.v](./spinal-cocotb/DmaMem.v), used in Caravel user project for post processing. +The `run.sh` script will generate the Verilog code, `DmaMem.v`, under [spinal-cocotb](./spinal-cocotb), which is used in Caravel user project for post-processing. `DmaMem.v` will be moved to the Verilog code directory of the Caravel user project. ### Caravel Code The Caravel user project follows the code structure of the example project exactly. Some important code files are: * [DmaMem.v](./verilog/rtl/DmaMem.v) is the Verilog code generated from `DmaMem.scala` by Spinal HDL; -* [axi_dma.v](./verilog/rtl/axi_dma.v) is the wrapper to `DmaMem.v`, and is instantiated in `user_project_wrapper.v`; +* [axi_dma.v](./verilog/rtl/axi_dma.v) is the wrapper to `DmaMem.v`, and is instantiated in `user_project_wrapper.v`; \ No newline at end of file
diff --git a/spinal-cocotb/SpinalNet/src/main/scala/dma/AxiMemorySim.scala b/spinal-cocotb/SpinalNet/src/main/scala/dma/AxiMemorySim.scala new file mode 100644 index 0000000..549d094 --- /dev/null +++ b/spinal-cocotb/SpinalNet/src/main/scala/dma/AxiMemorySim.scala
@@ -0,0 +1,601 @@ +package dma + +import spinal.core._ +import spinal.sim._ +import spinal.core.sim._ +import spinal.lib._ +import spinal.lib.bus.amba4.axi._ + +import scala.collection.mutable +import scala.collection.concurrent.TrieMap +import java.nio.file.Paths +import java.nio.file.Files +import java.awt.image.BufferedImage +import java.io.ByteArrayInputStream +import javax.imageio.ImageIO +import java.io.File +import java.awt.image.Raster +import java.awt.image.DataBufferByte +import scala.util.Random + +@SuppressWarnings( + Array( + "scalafix:DisableSyntax.var" + ) +) +class MemoryPage(size: Int) { + val data = new Array[Byte](size) + + def clear(value: Byte): Unit = { + data.transform(x => value) + } + + def read(offset: Int): Byte = { + this.data(offset) + } + + def write(offset: Int, data: Byte): Unit = { + this.data(offset) = data + } + + /** Reads an array from this page. + * + * @param offset Offset into page + * @return Byte array containing the read bytes. Reads may be limited by the page end. + */ + def readArray(offset: Int, len: Int): Array[Byte] = { + var length = scala.math.min(len, size - offset) + var data = new Array[Byte](length) + + for (i <- 0 until length) { + data(i) = this.data(offset + i) + } + + data + } + + /** Writes an array to this page. + * + * @param offset Offset into page. + * @param data The byte array. + * @return Number of bytes written. Writes may be limited by the page end. + */ + def writeArray(offset: Int, data: Array[Byte]): Int = { + var length = scala.math.min(data.length, size - offset) + + for (i <- 0 until length) { + this.data(offset + i) = data(i) + } + + length + } +} + +@SuppressWarnings( + Array( + "scalafix:DisableSyntax.null", + "scalafix:DisableSyntax.var" + ) +) +case class SparseMemory() { + val memory = Array.fill[MemoryPage](4096)(null) + + def allocPage(): MemoryPage = { + val page = new MemoryPage(1024 * 1024) + page.clear(0xcd.toByte) + page + } + + def invalidPage(): MemoryPage = { + val page = new MemoryPage(1024 * 1024) + page.clear(0xef.toByte) + page + } + + def getElseAllocPage(index: Int): MemoryPage = { + if (memory(index) == null) { + println(s"Adding page ${index} at 0x${(index << 20).toHexString}") + memory(index) = allocPage() + } + memory(index) + } + + def getElseInvalidPage(index: Int): MemoryPage = { + if (memory(index) == null) { + println( + s"Page fault while reading page ${index} (0x${(index << 20).toHexString})" + ) + invalidPage() + } else + memory(index) + } + + def getPageIndex(address: Long): Int = { + (address >> 20).toInt + } + + def getOffset(address: Long): Int = { + val mask = (1 << 20) - 1 + (address & mask).toInt + } + + def read(address: Long): Byte = { + getElseInvalidPage(getPageIndex(address)).read(getOffset(address)) + } + + def write(address: Long, data: Byte): Unit = { + getElseAllocPage(getPageIndex(address)).write(getOffset(address), data) + } + + def readArray(address: Long, len: Long): Array[Byte] = { + val startPageIndex = getPageIndex(address) + val endPageIndex = getPageIndex(address + len - 1) + var offset = getOffset(address) + val buffer = new mutable.ArrayBuffer[Byte](0) + + for (i <- startPageIndex to endPageIndex) { + val page = getElseInvalidPage(i) + val readArray = page.readArray(offset, len.toInt - buffer.length) + buffer.appendAll(readArray) + offset = 0 + } + + buffer.toArray + } + + def writeArray(address: Long, data: Array[Byte]): Unit = { + val startPageIndex = getPageIndex(address) + val endPageIndex = getPageIndex(address + data.length - 1) + var offset = getOffset(address) + + for (i <- startPageIndex to endPageIndex) { + val page = getElseAllocPage(i) + val bytesWritten = page.writeArray(offset, data) + data.drop(bytesWritten) + offset = 0 + } + } + + // 64-bit strobe, support bus width max to 512 bits + def writeArrayStrb(address: Long, data: Array[Byte], strb: Long): Unit = { + for (i <- 0 until data.length) { + val strobeBitSel = 1 << i + val strobe = strobeBitSel & strb + if (strobe > 0) { + val idx = getPageIndex(address + i) + val page = getElseAllocPage(idx) + val offset = getOffset(address + i) + page.write(offset, data(i)) + } + } + } + + /** Reads a BigInt value from the given address. + * + * @param address Read address. + * @param width Length of the byte array to be read in bytes. + * @return BigInt read from the given address. + */ + def readBigInt(address: Long, length: Int): BigInt = { + val dataArray = readArray(address, length) + val buffer = + dataArray.reverse.toBuffer // revert for Little Endian representation + + // We never want negative numbers + buffer.prepend(0.toByte) + + BigInt(buffer.toArray) + } + + /** Writes a BigInt value to the given address. + * The BigInt will be resized to a byte Array of given width. + * The data will be trimmed if it is bigger than the given width. + * If it is smaller, the unused bytes will be filled with '0x00'. + * + * @param address Write address. + * @param data Data to be written. + * @param width Width of the byte Array the data is resized to (if necessary). + */ + def writeBigInt(address: Long, data: BigInt, width: Int): Unit = { + var dataArray = data.toByteArray.reverse // Little endian? + var length = scala.math.min(width, dataArray.length) + var result = Array.fill[Byte](width)(0.toByte) + + for (i <- 0 until length) + result(i) = dataArray(i) + + writeArray(address, result) + } + + def writeBigIntStrb( + address: Long, + data: BigInt, + width: Int, + strb: Long + ): Unit = { + var dataArray = data.toByteArray.reverse // Little endian? + var length = scala.math.min(width, dataArray.length) + var result = Array.fill[Byte](width)(0.toByte) + + for (i <- 0 until length) + result(i) = dataArray(i) + + writeArrayStrb(address, result, strb) + } + + def loadBinary(address: Long, file: String): Unit = { + val byteArray = Files.readAllBytes(Paths.get(file)) + writeArray(address, byteArray) + + println( + s"Loading 0x${byteArray.length.toHexString} bytes from ${file} to 0x${address.toHexString}" + ) + } + + def loadDebugSequence(address: Long, length: Int, width: Int): Unit = { + for (i <- 0 until length) { + writeBigInt(address + i * width, BigInt(address + i * width), width) + } + } + + //def saveImage(address : Long, len : Long, file : String) : Unit = { + // val byteArray = readArray(address, len) + // val img = new BufferedImage(480, 640, BufferedImage.TYPE_INT_RGB) + // img.setData(Raster.createRaster(img.getSampleModel(), new DataBufferByte(byteArray, byteArray.length), null)) + // ImageIO.write(img, "png", new File(file)); + //} + + def saveBinary(address: Long, len: Long, file: String): Unit = { + val byteArray = readArray(address, len) + Files.write(Paths.get(file), byteArray) + + println( + s"Saving 0x${len.toHexString} bytes from 0x${address.toHexString} to ${file}" + ) + } +} + +case class AxiJob( + address: Long, + burstLength: Int, + id: Int +) { + // check for read/write over 4k boundary +} + +/** Configuration class for the AxiMemorySim. + * + * @param maxOutstandingReads + * @param maxOutstandingWrites + * @param useAlteraBehavior Couple write command and write channel as in the Altera Cyclone 5 F2H_SDRAM port. + */ +case class AxiMemorySimConfig( + maxOutstandingReads: Int = 8, + maxOutstandingWrites: Int = 8, + readResponseDelay: Int = 0, + writeResponseDelay: Int = 0, + interruptProbability: Int = 0, + interruptMaxDelay: Int = 0, + useAlteraBehavior: Boolean = false +) {} + +@SuppressWarnings( + Array( + "scalafix:DisableSyntax.var" + ) +) +case class AxiMemorySim( + axi: Axi4, + clockDomain: ClockDomain, + config: AxiMemorySimConfig +) { + val memory = SparseMemory() + val pending_reads = new mutable.Queue[AxiJob] + val pending_writes = new mutable.Queue[AxiJob] + + /** Bus word width in bytes */ + val busWordWidth = axi.config.dataWidth / 8 + + def newAxiJob(address: Long, burstLength: Int, id: Int): AxiJob = { + AxiJob(address, burstLength, id) + } + + def start(): Unit = { + fork { + handleAr(axi.ar) + } + + fork { + handleR(axi.r) + } + + if (config.useAlteraBehavior) { + fork { + handleAwAndW(axi.w, axi.aw, axi.b) + } + } else { + fork { + handleAw(axi.aw) + } + + fork { + handleW(axi.w, axi.b) + } + } + } + + def handleAr(ar: Stream[Axi4Ar]): Unit = { + println("Handling AXI4 Master read cmds...") + + ar.ready #= false + + while (true) { + ar.ready #= true + clockDomain.waitSamplingWhere(ar.valid.toBoolean) + ar.ready #= false + + // assert( + // assertion = (ar.payload.len.toBigInt + (ar.payload.addr.toBigInt & 4095)) <= 4095, + // message = s"Read request crossing 4k boundary (addr=${ar.payload.addr.toBigInt.toString(16)}, len=${ar.payload.len.toLong.toHexString}" + // ) + + assert( + assertion = 4096 >= ( + (ar.payload.len.toLong + 1) * scala.math + .pow(2, ar.payload.size.toInt - 3) + .toInt + (ar.payload.addr.toBigInt & 4095) + ), + message = s""" + Read request crossing 4k boundary ( + addr=${ar.payload.addr.toBigInt}, + len=${ar.payload.len.toLong + 1} + size=${ar.payload.size.toLong} + end=${(ar.payload.len.toLong + 1) * scala.math + .pow(2, ar.payload.size.toInt - 3) + .toInt + (ar.payload.addr.toBigInt & 4095)} + )""" + ) + + println( + s"create new AXI read job with id=${ar.payload.id.toInt}, addr=${ar.payload.addr.toLong}, burst len=${ar.payload.len.toInt}" + ) + pending_reads += newAxiJob( + ar.payload.addr.toLong, + ar.payload.len.toInt, + ar.payload.id.toInt + ) + + //println("AXI4 read cmd: addr=0x" + ar.payload.addr.toLong.toHexString + " count=" + (ar.payload.len.toBigInt+1)) + + if (pending_reads.length >= config.maxOutstandingReads) + clockDomain.waitSamplingWhere( + pending_reads.length < config.maxOutstandingReads + ) + } + } + + def handleR(r: Stream[Axi4R]): Unit = { + println("Handling AXI4 Master read resp...") + + val random = Random + + r.valid #= false + r.payload.last #= false + + while (true) { + clockDomain.waitSampling(1) + + // todo: implement read issuing delay + + if (pending_reads.nonEmpty) { + //var job = pending_reads.front + var job = pending_reads.dequeue() + + r.payload.id #= job.id + r.valid #= true + + var i = 0 + while (i <= job.burstLength) { + if (config.interruptProbability > random.nextInt(100)) { + r.valid #= false + clockDomain.waitSampling( + random.nextInt(config.interruptMaxDelay + 1) + ) + r.valid #= true + } else { + if (i == job.burstLength) + r.payload.last #= true + r.payload.data #= memory.readBigInt( + job.address + i * busWordWidth, + busWordWidth + ) + clockDomain.waitSamplingWhere( + r.ready.toBoolean + ) // BUG: must wait for RREADY + i = i + 1 + val addr = job.address + (i - 1) * busWordWidth + println( + f"AXI4 burst ${i}-th read: addr=$addr%d, data=${r.payload.data.toBigInt}%x, width=${axi.config.dataWidth}, len=${job.burstLength + 1}" + ) + } + } + + r.valid #= false + r.payload.last #= false + + //pending_reads.dequeue() + + println( + "AXI4 read rsp: addr=0x" + job.address.toLong.toHexString + " count=" + (job.burstLength + 1) + ) + } + } + } + + def handleAw(aw: Stream[Axi4Aw]): Unit = { + println("Handling AXI4 Master write cmds...") + + aw.ready #= false + + while (true) { + aw.ready #= true + clockDomain.waitSamplingWhere(aw.valid.toBoolean) + aw.ready #= false + + assert( + assertion = 4096 >= ( + (aw.payload.len.toLong + 1) * scala.math + .pow(2, aw.payload.size.toInt - 3) + .toInt + (aw.payload.addr.toBigInt & 4095) + ), + message = s""" + Write request crossing 4k boundary ( + addr=${aw.payload.addr.toBigInt}, + len=${aw.payload.len.toLong + 1} + size=${aw.payload.size.toLong} + end=${(aw.payload.len.toLong + 1) * scala.math + .pow(2, aw.payload.size.toInt - 3) + .toInt + (aw.payload.addr.toBigInt & 4095)} + )""" + ) + + println( + s"create new AXI write job with id=${aw.payload.id.toInt}, addr=${aw.payload.addr.toLong}, burst len=${aw.payload.len.toInt}" + ) + pending_writes += newAxiJob( + aw.payload.addr.toLong, + aw.payload.len.toInt, + aw.payload.id.toInt + ) + + //println("AXI4 write cmd: addr=0x" + aw.payload.addr.toLong.toHexString + " count=" + (aw.payload.len.toBigInt+1)) + + if (pending_writes.length >= config.maxOutstandingWrites) + clockDomain.waitSamplingWhere( + pending_writes.length < config.maxOutstandingWrites + ) + } + } + + def handleW(w: Stream[Axi4W], b: Stream[Axi4B]): Unit = { + println("Handling AXI4 Master write...") + + w.ready #= false + b.valid #= false + + while (true) { + clockDomain.waitSampling(10) + + if (pending_writes.nonEmpty) { + var job = pending_writes.front + var count = job.burstLength + + w.ready #= true + + for (i <- 0 to job.burstLength) { + clockDomain.waitSamplingWhere(w.valid.toBoolean) + // memory.writeBigInt(job.address + i * busWordWidth, w.payload.data.toBigInt, busWordWidth) + memory.writeBigIntStrb( + job.address + i * busWordWidth, + w.payload.data.toBigInt, + busWordWidth, + w.payload.strb.toLong + ) + val addr = job.address + i * busWordWidth + println( + f"AXI4 burst ${i}-th write: addr=$addr, strb=${w.payload.strb.toLong}%x, data=${w.payload.data.toBigInt}%x, width=$busWordWidth, len=${job.burstLength + 1}" + ) + } + w.ready #= false + + clockDomain.waitSampling(config.writeResponseDelay) + + b.valid #= true + b.payload.id #= job.id + b.payload.resp #= 0 + clockDomain.waitSamplingWhere(b.ready.toBoolean) + b.valid #= false + + pending_writes.dequeue() + + println( + "AXI4 write: addr=0x" + job.address.toLong.toHexString + " count=" + (job.burstLength + 1) + ) + } + } + } + + /** Handle write command, write, and write response channel as implemented + * by Altera/Intel on their Cyclone 5 platform. + * Their implementation behaves as all three channels are coupled. The + * implementation waits until all words for a write operation have been + * transfered. Then it asserts the AWREADY to accept the write command. + * After that, BVALID is asserted. + * + * @param w AXI write channel + * @param aw AXI write command channel + * @param b AXI write response channel + */ + def handleAwAndW( + w: Stream[Axi4W], + aw: Stream[Axi4Aw], + b: Stream[Axi4B] + ): Unit = { + println( + "Handling AXI4 Master write cmds and write (Altera/Intel behavior)..." + ) + + val random = Random + + aw.ready #= false + w.ready #= false + b.valid #= false + + while (true) { + clockDomain.waitSamplingWhere(aw.valid.toBoolean && w.valid.toBoolean) + w.ready #= true + + assert( + assertion = + (aw.payload.len.toBigInt + (aw.payload.addr.toBigInt & 4095)) <= 4095, + message = + s"Write request crossing 4k boundary (addr=${aw.payload.addr.toBigInt + .toString(16)}, len=${aw.payload.len.toLong.toHexString}" + ) + + var i = 0; + while (i <= aw.payload.len.toInt) { + if (config.interruptProbability > random.nextInt(100)) { + w.ready #= false + clockDomain.waitSampling(random.nextInt(config.interruptMaxDelay + 1)) + w.ready #= true + } else { + clockDomain.waitSamplingWhere(w.valid.toBoolean) + memory.writeBigInt( + aw.payload.addr.toLong + i * busWordWidth, + w.payload.data.toBigInt, + busWordWidth + ) + i = i + 1 + } + } + + aw.ready #= true + + clockDomain.waitSampling(1) + + aw.ready #= false + w.ready #= false + + // Handle write response + clockDomain.waitSampling(config.writeResponseDelay) + + b.valid #= true + b.payload.resp #= 0 + clockDomain.waitSamplingWhere(b.ready.toBoolean) + b.valid #= false + + //println("AXI4 write cmd: addr=0x" + aw.payload.addr.toLong.toHexString + " count=" + (aw.payload.len.toBigInt+1)) + } + } +}
diff --git a/spinal-cocotb/SpinalNet/src/main/scala/dma/DmaController.scala b/spinal-cocotb/SpinalNet/src/main/scala/dma/DmaController.scala new file mode 100644 index 0000000..4e71807 --- /dev/null +++ b/spinal-cocotb/SpinalNet/src/main/scala/dma/DmaController.scala
@@ -0,0 +1,754 @@ +package dma + +import spinal.core._ +import spinal.lib._ +import spinal.lib.bus.amba4.axi._ +import spinal.lib.fsm._ + +case class DmaConfig( + addressWidth: Int = 32, + bufDepth: Int = 24, + burstLen: Int = 16, + dataWidth: Int = 32, + littleEndien: Boolean = true, + idWidth: Int = 4, + xySizeMax: Int = 65536 +) { + val busByteSize = dataWidth / 8 + val burstLenWidth = 8 // log2Up(burstLen) + val burstByteSize = burstLen * busByteSize + val fullStrbBits = scala.math.pow(2, busByteSize).toInt - 1 // all bits valid + val xySizeWidth = log2Up(xySizeMax) + + require(dataWidth % 8 == 0, s"$dataWidth % 8 == 0 assert failed") + require(burstLen <= xySizeMax, s"$burstLen < $xySizeMax assert failed") + require(burstLen <= 256, s"$burstLen < 256 assert failed") + require( + xySizeWidth < addressWidth, + s"$xySizeWidth < $addressWidth assert failed" + ) + + val axiConfig = Axi4Config( + addressWidth = addressWidth, + dataWidth = dataWidth, + idWidth = idWidth, + useId = true, + useQos = false, + useRegion = false, + useLock = false, + useCache = false, + useProt = false + ) +} + +class DmaController(dmaConfig: DmaConfig) extends Component { + val io = new Bundle { + val param = slave(Param(dmaConfig.addressWidth, dmaConfig.xySizeWidth)) + val axi = master(Axi4(dmaConfig.axiConfig)) + val ctrl = slave(Ctrl()) + } + + // val buf = new StreamFifo( + // dataType = Bits(dmaConfig.dataWidth bits), + // depth = dmaConfig.bufDepth + // ) + + val read = new DmaRead(dmaConfig) + val write = new DmaWrite(dmaConfig) + read.io.ctrl.start := io.ctrl.start + read.io.ctrl.halt := io.ctrl.halt + read.io.param := io.param + //buf.io.push << read.io.dout + write.io.ctrl.start := io.ctrl.start + write.io.ctrl.halt := io.ctrl.halt + write.io.param := io.param + //write.io.din << buf.io.pop + write.io.din << read.io.dout.queue(dmaConfig.bufDepth) + + io.ctrl.busy := read.io.ctrl.busy || write.io.ctrl.busy + io.ctrl.done := write.io.ctrl.done // No need to consider read done + + io.axi << read.io.axiR + io.axi << write.io.axiW +} + +class DmaRead(dmaConfig: DmaConfig) extends Component { + val io = new Bundle { + val dout = master(Stream(Bits(dmaConfig.dataWidth bits))) + val param = slave(Param(dmaConfig.addressWidth, dmaConfig.xySizeWidth)) + val axiR = master(Axi4ReadOnly(dmaConfig.axiConfig)) + val ctrl = slave(Ctrl()) + } + io.ctrl.done := False + + val busyReg = Reg(Bool) init (False) + when(io.ctrl.start) { + busyReg := True + } elsewhen (io.ctrl.done) { + busyReg := False + } + io.ctrl.busy := busyReg + + val id = 3 + + val arValidReg = Reg(False) init (False) + val burstLenReg = Reg(UInt(dmaConfig.burstLenWidth bits)) init (0) + val nxtBurstLen = UInt(dmaConfig.burstLenWidth bits) + + val curRowAddrReg = Reg(UInt(dmaConfig.addressWidth bits)) init (0) + val alignOffsetReg = Reg(UInt(log2Up(dmaConfig.busByteSize) bits)) init (0) + val alignOffsetNxt = UInt( + log2Up(dmaConfig.busByteSize) bits + ) // The alignment offset of row start address to bus width + val curAlignedAddrReg = Reg(UInt(dmaConfig.addressWidth bits)) init (0) + val curAlignedRowAddr = curRowAddrReg - alignOffsetReg + val rowByteSize = io.param.xsize + val nxtAlignedAddr = + curAlignedAddrReg + (burstLenReg << log2Up(dmaConfig.busByteSize)) + val srcRowGap = io.param.xsize + io.param.srcystep + val nxtRowAddr = curRowAddrReg + srcRowGap + + val runSignal = ~io.ctrl.halt + val idMatch = io.axiR.r.id === id + + io.axiR.ar.id := id + io.axiR.ar.addr := curAlignedAddrReg // read addr + io.axiR.ar.len := burstLenReg - 1 + io.axiR.ar.size := log2Up(dmaConfig.dataWidth) + io.axiR.ar.burst := Axi4.burst.INCR + if (dmaConfig.axiConfig.useLock) { + io.axiR.ar.lock := Axi4.lock.NORMAL + } + if (dmaConfig.axiConfig.useCache) { + io.axiR.ar.cache := B(0, 2 bits) ## io.param.cf ## io.param.bf + } + if (dmaConfig.axiConfig.useProt) { + io.axiR.ar.prot := 2 + } + io.axiR.ar.valid := arValidReg + + val dataPreReg = Reg(Bits(dmaConfig.dataWidth bits)) init (0) + val curBeatBytes = UInt((log2Up(dmaConfig.busByteSize) + 1) bits) + val output = Bits(dmaConfig.dataWidth bits) + val doutValid = False + val rReady = False + + // Save read data to StreamFifo + io.dout.valid := doutValid + io.axiR.r.ready := rReady + io.dout.payload := output + + val rowReadCntReg = + Reg(UInt(dmaConfig.xySizeWidth bits)) init (0) // Number of rows read + val rowReadCntNxt = rowReadCntReg + 1 + val colByteReadCntReg = + Reg( + UInt(dmaConfig.xySizeWidth bits) + ) init (0) // Number of bytes read in a row + val colByteReadCntNxt = colByteReadCntReg + curBeatBytes + val colRemainByteCntReg = Reg( + UInt(dmaConfig.xySizeWidth bits) + ) init (rowByteSize) // Number of bytes left to read + val colRemainByteCntNxt = colRemainByteCntReg - curBeatBytes + + val rowFirstBurstReg = Reg(Bool) init (False) + val rowFirstBeatReg = Reg(Bool) init (False) + //val rowLastBeat = False//colRemainByteCntReg <= dmaConfig.busByteSize + val nxtRow = False + + val fsmR = new StateMachine { + val IDLE: State = new State with EntryPoint { + whenIsActive { + arValidReg := False + alignOffsetReg := 0 + burstLenReg := 0 + curRowAddrReg := io.param.sar - srcRowGap // TODO: refactor this + curAlignedAddrReg := 0 + + dataPreReg := 0 + + rowFirstBurstReg := False + rowFirstBeatReg := False + + rowReadCntReg := 0 + colByteReadCntReg := 0 + colRemainByteCntReg := rowByteSize + + when(runSignal && io.ctrl.start) { + arValidReg := True + alignOffsetReg := alignOffsetNxt + burstLenReg := nxtBurstLen + + curRowAddrReg := io.param.sar + curAlignedAddrReg := io.param.sar - alignOffsetNxt + //colRemainByteCntReg := rowByteSize + + rowFirstBeatReg := True + rowFirstBurstReg := True + nxtRow := True + + goto(AR) + } + } + } + + val AR: State = new State { // Send AXI read address + whenIsActive { + when(runSignal && io.axiR.ar.valid && io.axiR.ar.ready) { + arValidReg := False + when(alignOffsetReg =/= 0 && rowFirstBeatReg) { + goto(FR) + } otherwise { + goto(BR) + } + } + } + } + + val FR: State = new State { // First read, read non-aligned data + whenIsActive { + rReady := True + doutValid := False // No output, just cache first read + + when(runSignal && io.axiR.r.valid && io.axiR.r.ready) { + colByteReadCntReg := colByteReadCntNxt + colRemainByteCntReg := colRemainByteCntNxt + rowFirstBeatReg := False + + when(io.axiR.r.last) { + // Minimum number of bytes to transfer is 2 * busByteSize + curAlignedAddrReg := nxtAlignedAddr + burstLenReg := nxtBurstLen + arValidReg := True + + goto(AR) + } otherwise { + goto(BR) + } + } + } + } + + val BR: State = new State { // Burst aligned read + whenIsActive { + doutValid := io.axiR.r.valid + rReady := io.dout.ready + + when(runSignal && io.axiR.r.valid && io.axiR.r.ready) { + colByteReadCntReg := colByteReadCntNxt + colRemainByteCntReg := colRemainByteCntNxt + rowFirstBeatReg := False + } + + when( + runSignal && io.axiR.r.valid && io.axiR.r.ready && io.axiR.r.last + ) { // Prepare next read address + rowFirstBurstReg := False + //burstLenReg := nxtBurstLen + + when(colByteReadCntNxt < rowByteSize) { // Continue read same row + curAlignedAddrReg := nxtAlignedAddr + burstLenReg := nxtBurstLen + arValidReg := True + + goto(AR) + } otherwise { // Finish read one row + //rowLastBeat := True + colByteReadCntReg := 0 + colRemainByteCntReg := rowByteSize + + when(alignOffsetReg =/= 0) { + goto(LAST) + } elsewhen (rowReadCntNxt < io.param.ysize) { + burstLenReg := nxtBurstLen + //colRemainByteCntReg := rowByteSize + + rowReadCntReg := rowReadCntNxt + rowFirstBeatReg := True + rowFirstBurstReg := True + + nxtRow := True + alignOffsetReg := alignOffsetNxt + curAlignedAddrReg := nxtRowAddr - alignOffsetNxt + curRowAddrReg := nxtRowAddr + + arValidReg := True + goto(AR) + } otherwise { // Finish read all rows + io.ctrl.done := True + goto(IDLE) + } + } + } + } + } + + val LAST: State = new State { // Send last beat when non-aligned read + whenIsActive { + rReady := False + doutValid := True + when(runSignal && io.dout.valid && io.dout.ready) { + when(rowReadCntNxt < io.param.ysize) { // Send next row address + burstLenReg := nxtBurstLen + //colRemainByteCntReg := rowByteSize + + rowReadCntReg := rowReadCntNxt + rowFirstBeatReg := True + rowFirstBurstReg := True + + nxtRow := True + alignOffsetReg := alignOffsetNxt + curAlignedAddrReg := nxtRowAddr - alignOffsetNxt + curRowAddrReg := nxtRowAddr + + arValidReg := True + goto(AR) + } otherwise { // Finish read all rows + io.ctrl.done := True + goto(IDLE) + } + } + } + } + } + + val computeNextReadBurstLen = new Area { + if (dmaConfig.busByteSize > 1) { + alignOffsetNxt := nxtRowAddr((log2Up(dmaConfig.busByteSize) - 1) downto 0) + } else { + alignOffsetNxt := 0 + } + + val nxtAlignedRowAddr = nxtRowAddr - alignOffsetNxt + val tmpBurstByteSizeIn4K = (4096 - nxtAlignedRowAddr(0, 12 bits)) + val nxtAlignedRowByteSize = rowByteSize + alignOffsetNxt + val rowCross4K = (tmpBurstByteSizeIn4K < dmaConfig.burstByteSize + && tmpBurstByteSizeIn4K < nxtAlignedRowByteSize) + when(nxtRow) { + when(rowCross4K) { + nxtBurstLen := (tmpBurstByteSizeIn4K >> (log2Up( + dmaConfig.busByteSize + ))).resized + } elsewhen (dmaConfig.burstByteSize < nxtAlignedRowByteSize) { + nxtBurstLen := dmaConfig.burstLen + } otherwise { + when( + nxtAlignedRowByteSize( + (log2Up(dmaConfig.busByteSize) - 1) downto 0 + ) =/= 0 + ) { + nxtBurstLen := ((nxtAlignedRowByteSize >> (log2Up( + dmaConfig.busByteSize + ))) + 1).resized + } otherwise { + nxtBurstLen := (nxtAlignedRowByteSize >> (log2Up( + dmaConfig.busByteSize + ))).resized + } + } + } elsewhen (colRemainByteCntNxt < dmaConfig.burstByteSize) { + when( + colRemainByteCntReg((log2Up(dmaConfig.busByteSize) - 1) downto 0) =/= 0 + ) { + nxtBurstLen := ((colRemainByteCntNxt >> (log2Up( + dmaConfig.busByteSize + ))) + 1).resized + } otherwise { + nxtBurstLen := (colRemainByteCntNxt >> (log2Up( + dmaConfig.busByteSize + ))).resized + } + } otherwise { + nxtBurstLen := dmaConfig.burstLen + } + } + + val computeNextReadPayload = new Area { + // Burst length is at least 2, the minimum data size to transfer is twice bus bytes + when(rowFirstBeatReg) { + // Mask padding bits as invalid for first write beat + curBeatBytes := dmaConfig.busByteSize - alignOffsetReg + // } elsewhen (rowLastBeat) { + // // colRemainByteCntReg is smaller than dmaConfig.busByteSize + // curBeatBytes := (rowByteSize - colByteReadCntReg).resized//colRemainByteCntReg.resized + } otherwise { + curBeatBytes := dmaConfig.busByteSize + } + + when(runSignal && io.axiR.r.valid && io.axiR.r.ready) { + dataPreReg := io.axiR.r.data + } + + when(alignOffsetReg =/= 0) { + switch(alignOffsetReg) { + for (off <- 0 until dmaConfig.busByteSize) { + is(off) { + val paddingWidth = off << log2Up(8) // off * 8 + val restWidth = + (dmaConfig.busByteSize - off) << log2Up( + 8 + ) // (busByteSize - off) * 8 + + if (dmaConfig.littleEndien) { + output := io.axiR.r.data(0, paddingWidth bits) ## dataPreReg( + paddingWidth, + restWidth bits + ) + } else { + output := dataPreReg(paddingWidth, restWidth bits) ## io.axiR.r + .data(0, paddingWidth bits) + } + } + } + } + } otherwise { + output := io.axiR.r.data + } + } +} + +class DmaWrite(dmaConfig: DmaConfig) extends Component { + val io = new Bundle { + val din = slave(Stream(Bits(dmaConfig.dataWidth bits))) + val param = slave(Param(dmaConfig.addressWidth, dmaConfig.xySizeWidth)) + val axiW = master(Axi4WriteOnly(dmaConfig.axiConfig)) + val ctrl = slave(Ctrl()) + } + + io.ctrl.done := False + + val busyReg = Reg(Bool) init (False) + when(io.ctrl.start) { + busyReg := True + } elsewhen (io.ctrl.done) { + busyReg := False + } + io.ctrl.busy := busyReg + + val id = 5 + + val awValidReg = Reg(False) init (False) + val burstLenReg = Reg(UInt(dmaConfig.burstLenWidth bits)) init (0) + val nxtBurstLen = UInt(dmaConfig.burstLenWidth bits) + + val curRowAddrReg = Reg(UInt(dmaConfig.addressWidth bits)) init (0) + val alignOffsetReg = Reg(UInt(log2Up(dmaConfig.busByteSize) bits)) init (0) + val alignOffsetNxt = UInt( + log2Up(dmaConfig.busByteSize) bits + ) // The alignment offset of row start address to bus width + val curAlignedAddrReg = Reg(UInt(dmaConfig.addressWidth bits)) init (0) + val curAlignedRowAddr = curRowAddrReg - alignOffsetReg + val rowByteSize = io.param.xsize + val nxtAlignedAddr = + curAlignedAddrReg + (burstLenReg << log2Up(dmaConfig.busByteSize)) + val dstRowGap = io.param.xsize + io.param.dstystep + val nxtRowAddr = curRowAddrReg + dstRowGap + val beatCnt = Counter( + start = 0, + end = dmaConfig.burstLen + ) // Count how many transfers in a burst + + val runSignal = ~io.ctrl.halt + val idMatch = io.axiW.b.id === id + + io.axiW.aw.id := id + io.axiW.aw.addr := curAlignedAddrReg // write addr + io.axiW.aw.len := burstLenReg - 1 + io.axiW.aw.size := log2Up(dmaConfig.dataWidth) + io.axiW.aw.burst := Axi4.burst.INCR + if (dmaConfig.axiConfig.useProt) { + io.axiW.aw.lock := Axi4.lock.NORMAL + } + if (dmaConfig.axiConfig.useProt) { + io.axiW.aw.cache := B(0, 2 bits) ## io.param.cf ## io.param.bf + } + if (dmaConfig.axiConfig.useProt) { + io.axiW.aw.prot := 2 // Unprivileged non-secure data access + } + io.axiW.aw.valid := awValidReg + + val bReadyReg = Reg(Bool) init (False) + io.axiW.b.ready := bReadyReg + + io.axiW.w.last := beatCnt === (burstLenReg - 1) + when(io.axiW.w.last) { + beatCnt.clear() + } + + val dinPrevReg = Reg(Bits(dmaConfig.dataWidth bits)) init (0) + val curBeatBytes = UInt((log2Up(dmaConfig.busByteSize) + 1) bits) + val strobe = UInt(dmaConfig.busByteSize bits) + val payload = Bits(dmaConfig.dataWidth bits) + val dinReady = False + val wValid = False + + // Send write data to AXI write channel + io.axiW.w.valid := wValid + io.din.ready := dinReady + io.axiW.w.data := payload + io.axiW.w.strb := strobe.asBits + + val rowWriteCntReg = + Reg(UInt(dmaConfig.xySizeWidth bits)) init (0) // Number of rows write + val rowWriteCntNxt = rowWriteCntReg + 1 + val colByteWriteCntReg = + Reg( + UInt(dmaConfig.xySizeWidth bits) + ) init (0) // Number of bytes written in a row + val colByteWriteCntNxt = + colByteWriteCntReg + curBeatBytes // Each burst beat transfers bus width data including invalid ones + val colRemainByteCntReg = Reg( + UInt(dmaConfig.xySizeWidth bits) + ) init (rowByteSize) // Number of bytes left to write + val colRemainByteCntNxt = colRemainByteCntReg - curBeatBytes + + val rowFirstBurstReg = Reg(Bool) init (False) + val rowFirstBeatReg = Reg(Bool) init (False) + val rowLastBeat = colRemainByteCntReg <= dmaConfig.busByteSize + val nxtRow = False + + val fsmW = new StateMachine { + val IDLE: State = new State with EntryPoint { + whenIsActive { + awValidReg := False + alignOffsetReg := 0 + burstLenReg := 0 + curRowAddrReg := io.param.dar - dstRowGap // TODO: refactor this + curAlignedAddrReg := 0 + + bReadyReg := False + dinPrevReg := 0 + + rowFirstBurstReg := False + rowFirstBeatReg := False + + rowWriteCntReg := 0 + colByteWriteCntReg := 0 + colRemainByteCntReg := rowByteSize + + when(runSignal && io.ctrl.start) { + awValidReg := True + alignOffsetReg := alignOffsetNxt + burstLenReg := nxtBurstLen + + curRowAddrReg := io.param.dar + curAlignedAddrReg := io.param.dar - alignOffsetNxt + //colRemainByteCntReg := rowByteSize + + rowFirstBeatReg := True + rowFirstBurstReg := True + nxtRow := True + //goto(AW) + goto(W) + } + } + } + /* + val AW: State = new State { // Send AXI write address + whenIsActive { + when (runSignal && io.axiW.aw.valid && io.axiW.aw.ready) { + awValidReg := False + goto(W) + } + } + } + */ + val W: State = new State { + onEntry { + beatCnt.clear() + } + whenIsActive { + when(runSignal && io.axiW.aw.valid && io.axiW.aw.ready) { + awValidReg := False + } + + dinReady := io.axiW.w.ready + wValid := io.din.valid + + when(runSignal && io.axiW.w.valid && io.axiW.w.ready) { + beatCnt.increment() + + colByteWriteCntReg := colByteWriteCntNxt + colRemainByteCntReg := colRemainByteCntNxt + rowFirstBeatReg := False + } + + when(runSignal) { + when(io.axiW.w.valid && io.axiW.w.ready) { + when(io.axiW.w.last) { + rowFirstBurstReg := False + beatCnt.clear() + + bReadyReg := True + goto(B) + } elsewhen (alignOffsetReg =/= 0 && colRemainByteCntNxt < dmaConfig.busByteSize) { + goto(LAST) + } + } + } + } + } + + val LAST: State = new State { + whenIsActive { + dinReady := False + wValid := True + + when( + runSignal && io.axiW.w.last && io.axiW.w.valid && io.axiW.w.ready + ) { + rowFirstBurstReg := False + beatCnt.clear() + + bReadyReg := True + goto(B) + } + } + } + + val B: State = new State { + whenIsActive { + // TODO: handle BRESP error + when(runSignal && io.axiW.b.valid && io.axiW.b.ready) { // Prepare next write address + bReadyReg := False + burstLenReg := nxtBurstLen + + when(colByteWriteCntReg < rowByteSize) { // Continue write same row + curAlignedAddrReg := nxtAlignedAddr + awValidReg := True + + //goto(AW) + goto(W) + } otherwise { // Finish write one row + colByteWriteCntReg := 0 + colRemainByteCntReg := rowByteSize + + when(rowWriteCntNxt < io.param.ysize) { + rowWriteCntReg := rowWriteCntNxt + rowFirstBeatReg := True + rowFirstBurstReg := True + + nxtRow := True + alignOffsetReg := alignOffsetNxt + curAlignedAddrReg := nxtRowAddr - alignOffsetNxt + curRowAddrReg := nxtRowAddr + + awValidReg := True + //goto(AW) + goto(W) + } otherwise { // Finish write all rows + io.ctrl.done := True + goto(IDLE) + } + } + } + } + } + } + + val computeNextWriteBurstLen = new Area { + if (dmaConfig.busByteSize > 1) { + alignOffsetNxt := nxtRowAddr((log2Up(dmaConfig.busByteSize) - 1) downto 0) + } else { + alignOffsetNxt := 0 + } + + val nxtAlignedRowAddr = nxtRowAddr - alignOffsetNxt + val tmpBurstByteSizeIn4K = (4096 - nxtAlignedRowAddr(0, 12 bits)) + val nxtAlignedRowByteSize = rowByteSize + alignOffsetNxt + val rowCross4K = (tmpBurstByteSizeIn4K < dmaConfig.burstByteSize + && tmpBurstByteSizeIn4K < nxtAlignedRowByteSize) + when(nxtRow) { + when(rowCross4K) { + nxtBurstLen := (tmpBurstByteSizeIn4K >> (log2Up( + dmaConfig.busByteSize + ))).resized + } elsewhen (dmaConfig.burstByteSize < nxtAlignedRowByteSize) { + nxtBurstLen := dmaConfig.burstLen + } otherwise { + when( + nxtAlignedRowByteSize( + (log2Up(dmaConfig.busByteSize) - 1) downto 0 + ) =/= 0 + ) { + nxtBurstLen := ((nxtAlignedRowByteSize >> (log2Up( + dmaConfig.busByteSize + ))) + 1).resized + } otherwise { + nxtBurstLen := (nxtAlignedRowByteSize >> (log2Up( + dmaConfig.busByteSize + ))).resized + } + } + } elsewhen (colRemainByteCntReg < dmaConfig.burstByteSize) { + when( + colRemainByteCntReg((log2Up(dmaConfig.busByteSize) - 1) downto 0) =/= 0 + ) { + nxtBurstLen := ((colRemainByteCntReg >> (log2Up( + dmaConfig.busByteSize + ))) + 1).resized + } otherwise { + nxtBurstLen := (colRemainByteCntReg >> (log2Up( + dmaConfig.busByteSize + ))).resized + } + } otherwise { + nxtBurstLen := dmaConfig.burstLen + } + } + + val computeNextWritePayload = new Area { + // Burst length is at least 2, the minimum data to transfer is twice bus bytes + when(rowFirstBeatReg) { + // Mask padding bits as invalid for first write beat + strobe := (dmaConfig.fullStrbBits - ((U(1) << alignOffsetReg) - 1)) + curBeatBytes := dmaConfig.busByteSize - alignOffsetReg + } elsewhen (rowLastBeat) { + // In this case colRemainByteCntReg is smaller than dmaConfig.busByteSize + strobe := ((U(1) << colRemainByteCntReg) - 1).resized + curBeatBytes := colRemainByteCntReg.resized + } otherwise { + strobe := dmaConfig.fullStrbBits + curBeatBytes := dmaConfig.busByteSize + } + + when(io.din.valid && io.din.ready) { + dinPrevReg := io.din.payload + } + + switch(alignOffsetReg) { + for (off <- 0 until dmaConfig.busByteSize) { + is(off) { + val paddingWidth = off << log2Up(8) // off * 8 + val restWidth = + (dmaConfig.busByteSize - off) << log2Up( + 8 + ) // (busByteSize - off) * 8 + + if (dmaConfig.littleEndien) { + payload := io.din.payload(0, restWidth bits) ## dinPrevReg( + restWidth, + paddingWidth bits + ) + } else { + payload := dinPrevReg(restWidth, paddingWidth bits) ## io.din + .payload(0, restWidth bits) + } + } + } + } + } +} + +object DmaController { + def main(args: Array[String]): Unit = { + val dmaConfig = DmaConfig( + addressWidth = 16, + burstLen = 8, + bufDepth = 24, + dataWidth = 8, + xySizeMax = 256 + ) + SpinalVerilog(new DmaController(dmaConfig)) printPruned () + } +}
diff --git a/spinal-cocotb/SpinalNet/src/main/scala/dma/DmaControllerSim.scala b/spinal-cocotb/SpinalNet/src/main/scala/dma/DmaControllerSim.scala new file mode 100644 index 0000000..826444f --- /dev/null +++ b/spinal-cocotb/SpinalNet/src/main/scala/dma/DmaControllerSim.scala
@@ -0,0 +1,139 @@ +package dma + +import spinal.core._ +import spinal.core.sim._ +import spinal.lib._ +import spinal.lib.bus.amba4.axi._ +// import spinal.lib.bus.amba4.axi.sim._ +import spinal.lib.sim._ + +import scala.collection.mutable + +object DmaControllerSim extends App { + val dmaConfig = DmaConfig( + addressWidth = 32, + burstLen = 8, + bufDepth = 32, + dataWidth = 32, + xySizeMax = 256 + ) + + def runDma( + dut: DmaController, + axiMem: AxiMemorySim, + sar: Int, + dar: Int, + xsize: Int, + ysize: Int, + srcystep: Int, + dstystep: Int + ): Unit = { + val queue = mutable.Queue[Int]() + + dut.io.ctrl.start #= false + dut.io.ctrl.halt #= false + + dut.io.param.sar #= sar + dut.io.param.dar #= dar + dut.io.param.xsize #= xsize // At least bus byte size + dut.io.param.ysize #= ysize + dut.io.param.srcystep #= srcystep + dut.io.param.dstystep #= dstystep + dut.io.param.llr #= 0 + dut.io.param.bf #= true + dut.io.param.cf #= true + + sleep(0) // Make io.param assignment effective + // val axiMem = AxiMemorySim( + // dut.io.axi, + // dut.clockDomain, + // AxiMemorySimConfig(writeResponseDelay = 0) + // ) + + // Prepare memory data + val srcBeginAddr = dut.io.param.sar.toLong + val srcRowGap = dut.io.param.xsize.toInt + dut.io.param.srcystep.toInt + val rowSize = dut.io.param.xsize.toInt + for (y <- 0 until dut.io.param.ysize.toInt) { + for (x <- 0 until dut.io.param.xsize.toInt) { + val inc = y * srcRowGap + x + val d = (y * rowSize + x) % 128 + val addr = srcBeginAddr + inc + + axiMem.memory.write(addr, d.toByte) + println(f"prepare: addr=$addr, data=${d}=${d}%x") + queue.enqueue(d) + } + } + + dut.clockDomain.waitSampling(10) + dut.io.ctrl.start #= true + axiMem.start() + dut.clockDomain.waitSampling() + dut.io.ctrl.start #= false + + waitUntil(dut.io.ctrl.done.toBoolean) + dut.clockDomain.waitSampling(2) + + val dstBeginAddr = dut.io.param.dar.toLong + val dstRowGap = dut.io.param.xsize.toInt + dut.io.param.dstystep.toInt + for (y <- 0 until dut.io.param.ysize.toInt) { + for (x <- 0 until dut.io.param.xsize.toInt) { + val inc = y * dstRowGap + x + val addr = dstBeginAddr + inc + + val b = axiMem.memory.read(addr) + println(f"check: addr=$addr, data=${b.toInt}=${b.toInt}%x") + val t = queue.dequeue() + assert(b.toInt == t, s"${b.toInt}==${t} assert failed") + } + } + } + + SimConfig.withWave + .compile(new DmaController(dmaConfig)) + .doSim { dut => + val axiMem = AxiMemorySim( + dut.io.axi, + dut.clockDomain, + AxiMemorySimConfig(writeResponseDelay = 0) + ) + + dut.clockDomain.forkStimulus(5) + + val sar = 4091 + val dar = 8183 + val xsize = 57 // At least twice bus byte size, minimum burst length is 2 + val ysize = 2 + val srcystep = 5 + val dstystep = 9 + runDma( + dut, + axiMem, + sar, + dar, + xsize, + ysize, + srcystep, + dstystep + ) + + // dut.clockDomain.waitSampling(2) + // sar = 8183 + // dar = 4091 + // // xsize = 57 // At least bus byte size + // // ysize = 2 + // // srcystep = 5 + // // dstystep = 9 + // runDma( + // dut, + // axiMem, + // sar, + // dar, + // xsize, + // ysize, + // srcystep, + // dstystep + // ) + } +}
diff --git a/spinal-cocotb/SpinalNet/src/main/scala/dma/DmaIf.scala b/spinal-cocotb/SpinalNet/src/main/scala/dma/DmaIf.scala new file mode 100644 index 0000000..2375dbb --- /dev/null +++ b/spinal-cocotb/SpinalNet/src/main/scala/dma/DmaIf.scala
@@ -0,0 +1,53 @@ +package dma + +import spinal.core._ +import spinal.lib._ + +//--- control +case class Ctrl() extends Bundle with IMasterSlave { + val start = Bool + val busy = Bool + val done = Bool + val halt = Bool + + override def asMaster(): Unit = { + out(start, halt) + in(busy, done) + } +} + +case class Param(addressWidth: Int, xySizeWidth: Int) + extends Bundle + with IMasterSlave { + val sar = UInt(addressWidth bits) // source byte addr + val dar = UInt(addressWidth bits) // destination byte addr + val xsize = UInt( + xySizeWidth bits + ) // 2D DMA x-dir transfer byte size, cnt from 0 + val ysize = UInt(xySizeWidth bits) // 2D DMA y-dir transfer lines, cnt fom 0 + val srcystep = UInt( + xySizeWidth bits + ) // source byte addr offset between each line, cnt from 1 + val dstystep = UInt( + xySizeWidth bits + ) // destination byte addr offset between each line, cnt from 1 + val llr = UInt( + addressWidth bits + ) // DMA cmd linked list base addr (addr pointer) + val bf = Bool // bufferable flag in AXI cmd + val cf = Bool // cacheable flag in AXI cmd + + override def asMaster(): Unit = { + out( + sar, + dar, + xsize, + ysize, + srcystep, + dstystep, + llr, + bf, + cf + ) + } +}
diff --git a/spinal-cocotb/SpinalNet/src/main/scala/dma/DmaMem.scala b/spinal-cocotb/SpinalNet/src/main/scala/dma/DmaMem.scala new file mode 100644 index 0000000..ccfe3d9 --- /dev/null +++ b/spinal-cocotb/SpinalNet/src/main/scala/dma/DmaMem.scala
@@ -0,0 +1,104 @@ +package dma + +import spinal.core._ +import spinal.lib._ +import spinal.lib.bus.wishbone._ +import spinal.lib.bus.amba4.axi._ +import spinal.lib.bus.regif.AccessType._ +import spinal.lib.memory.sdram.sdr.{MT48LC16M16A2, SdramInterface} + +import sdram._ + +class DmaMem( + addressWidth: Int = 32, + bufSize: Int = 16, + burstLen: Int = 8, + dataWidth: Int = 32, + idWidth: Int = 4, + selWidth: Int = 4 +) extends Component { + val axiConfig = Axi4Config( + addressWidth = addressWidth, //log2Up(memByteSize) + dataWidth = dataWidth, + idWidth = idWidth, + useLock = false, + useRegion = false, + useCache = false, + useProt = false, + useQos = false + ) + + val wbConfig = WishboneConfig( + addressWidth = addressWidth, //log2Up(memByteSize), + dataWidth = dataWidth, + selWidth = selWidth + ) + + val dmaConfig = DmaConfig( + addressWidth = addressWidth, + burstLen = burstLen, + bufDepth = bufSize, + dataWidth = dataWidth, + xySizeMax = 256 + ) + + val sdramConfig = SdramConfig( + CAS = 2, + addressWidth = addressWidth, + burstLen = burstLen, + busDataWidth = dataWidth, + idWidth = idWidth + ) + + val sdramDevice = MT48LC16M16A2 + + val io = new Bundle { + val ctrl = slave(Ctrl()) + val sdram = master(SdramInterface(sdramDevice.layout)) + val wb = slave( + Wishbone(wbConfig) + ) + } + + val busif = BusInterface(io.wb, (0, 100 Byte)) + val SAR_REG = busif.newReg(doc = "DMA src address") + val DAR_REG = busif.newReg(doc = "DMA dst address") + + val srcAddr = SAR_REG.field(addressWidth bits, RW) + val dstAddr = DAR_REG.field(addressWidth bits, RW) + + val dmaArea = new Area { + val dmaController = new DmaController(dmaConfig) + dmaController.io.ctrl <> io.ctrl + + dmaController.io.param.sar := srcAddr.asUInt + dmaController.io.param.dar := dstAddr.asUInt + dmaController.io.param.xsize := 2 * dmaConfig.busByteSize // At least twice bus byte size + dmaController.io.param.ysize := 1 + dmaController.io.param.srcystep := 0 + dmaController.io.param.dstystep := 0 + dmaController.io.param.llr := 0 + dmaController.io.param.bf := True + dmaController.io.param.cf := True + } + + val sdramArea = new Area { + val sdramController = new SdramController( + sdramDevice.layout, + sdramDevice.timingGrade7, + SdramConfig() + ) + io.sdram <> sdramController.io.sdram + } + + sdramArea.sdramController.io.axi << dmaArea.dmaController.io.axi +} + +object DmaMem { + def main(args: Array[String]): Unit = { + SpinalConfig(defaultClockDomainFrequency = FixedFrequency(100 MHz)) + .generateVerilog( + new DmaMem() + ) printPruned () + } +}
diff --git a/spinal-cocotb/SpinalNet/src/main/scala/dma/WishboneBusInterface.scala b/spinal-cocotb/SpinalNet/src/main/scala/dma/WishboneBusInterface.scala new file mode 100644 index 0000000..64b3774 --- /dev/null +++ b/spinal-cocotb/SpinalNet/src/main/scala/dma/WishboneBusInterface.scala
@@ -0,0 +1,91 @@ +package dma + +import spinal.core._ +import spinal.lib.bus.misc.SizeMapping +import spinal.lib.bus.regif.{BusIf, ClassName} +import spinal.lib.bus.wishbone.Wishbone + +case class WishboneBusInterface( + bus: Wishbone, + sizeMap: SizeMapping, + selId: Int = 0, + readSync: Boolean = true, + regPre: String = "" +)(implicit moduleName: ClassName) + extends BusIf { + override def getModuleName = moduleName.name + + val readError = Bool() + val readData = Bits(bus.config.dataWidth bits) + + if (readSync) { + readError.setAsReg() init False + readData.setAsReg() init 0 + } else { + readError := False + readData := 0 + } + + bus.ACK := True + bus.DAT_MISO := readData + if (bus.config.useERR) bus.ERR := readError + + val selMatch = if (bus.config.useSEL) bus.SEL(selId) else True + val askWrite = (selMatch && bus.CYC && bus.STB && bus.WE).allowPruning() + val askRead = (selMatch && bus.CYC && bus.STB && !bus.WE).allowPruning() + val doWrite = + (selMatch && bus.CYC && bus.STB && bus.ACK && bus.WE).allowPruning() + val doRead = + (selMatch && bus.CYC && bus.STB && bus.ACK && !bus.WE).allowPruning() + val writeData = bus.DAT_MISO + + override def readAddress() = bus.ADR + override def writeAddress() = bus.ADR + + override def readHalt() = bus.ACK := False + override def writeHalt() = bus.ACK := False + + override def busDataWidth = bus.config.dataWidth +} + +object BusInterface { + def apply(bus: Wishbone, sizeMap: SizeMapping)(implicit + moduleName: ClassName + ): BusIf = WishboneBusInterface(bus, sizeMap)(moduleName) + def apply(bus: Wishbone, sizeMap: SizeMapping, selID: Int)(implicit + moduleName: ClassName + ): BusIf = WishboneBusInterface(bus, sizeMap, selID)(moduleName) + def apply(bus: Wishbone, sizeMap: SizeMapping, selID: Int, regPre: String)( + implicit moduleName: ClassName + ): BusIf = + WishboneBusInterface(bus, sizeMap, selID, regPre = regPre)(moduleName) + def apply(bus: Wishbone, sizeMap: SizeMapping, selID: Int, readSync: Boolean)( + implicit moduleName: ClassName + ): BusIf = WishboneBusInterface(bus, sizeMap, selID, readSync)(moduleName) + def apply( + bus: Wishbone, + sizeMap: SizeMapping, + selID: Int, + readSync: Boolean, + regPre: String + )(implicit moduleName: ClassName): BusIf = + WishboneBusInterface(bus, sizeMap, selID, readSync, regPre = regPre)( + moduleName + ) + +// def apply(bus: Apb3, sizeMap: SizeMapping, selID: Int)(implicit moduleName: ClassName): BusIf = Apb3BusInterface(bus, sizeMap, selID)(moduleName) +// def apply(bus: Apb3, sizeMap: SizeMapping, selID: Int, regPre: String)(implicit moduleName: ClassName): BusIf = Apb3BusInterface(bus, sizeMap, selID, regPre = regPre)(moduleName) +// def apply(bus: Apb3, sizeMap: SizeMapping, selID: Int, readSync: Boolean)(implicit moduleName: ClassName): BusIf = Apb3BusInterface(bus, sizeMap, selID, readSync)(moduleName) +// def apply(bus: Apb3, sizeMap: SizeMapping, selID: Int, readSync: Boolean, regPre: String)(implicit moduleName: ClassName): BusIf = Apb3BusInterface(bus, sizeMap, selID, readSync, regPre = regPre)(moduleName) + +// def apply(bus: AhbLite3, sizeMap: SizeMapping)(implicit moduleName: ClassName): BusIf = AhbLite3BusInterface(bus, sizeMap)(moduleName) +// def apply(bus: AhbLite3, sizeMap: SizeMapping, regPre: String)(implicit moduleName: ClassName): BusIf = AhbLite3BusInterface(bus, sizeMap, regPre = regPre)(moduleName) +// def apply(bus: AhbLite3, sizeMap: SizeMapping, readSync: Boolean)(implicit moduleName: ClassName): BusIf = AhbLite3BusInterface(bus, sizeMap, readSync)(moduleName) +// def apply(bus: AhbLite3, sizeMap: SizeMapping, readSync: Boolean, regPre: String)(implicit moduleName: ClassName): BusIf = AhbLite3BusInterface(bus, sizeMap, readSync, regPre = regPre)(moduleName) + +// def apply(bus: Axi4, sizeMap: SizeMapping): BusIf = Axi4BusInterface(bus, sizeMap) +// def apply(bus: Axi4, sizeMap: SizeMapping, readSync: Boolean): BusIf = Axi4BusInterface(bus, sizeMap) +// +// def apply(bus: AxiLite4, sizeMap: SizeMapping): BusIf = AxiLite4BusInterface(bus, sizeMap) +// def apply(bus: AxiLite4, sizeMap: SizeMapping, readSync: Boolean): BusIf = AxiLite4BusInterface(bus, sizeMap) +}
diff --git a/spinal-cocotb/SpinalNet/src/main/scala/sdram/SdramController.scala b/spinal-cocotb/SpinalNet/src/main/scala/sdram/SdramController.scala new file mode 100644 index 0000000..786f4c0 --- /dev/null +++ b/spinal-cocotb/SpinalNet/src/main/scala/sdram/SdramController.scala
@@ -0,0 +1,553 @@ +package sdram + +import scala.math.BigDecimal.RoundingMode +import spinal.core._ +import spinal.lib._ +import spinal.lib.bus.amba4.axi._ +import spinal.lib.fsm._ +import spinal.lib.io.TriStateArray +import spinal.lib.memory.sdram._ +import spinal.lib.memory.sdram.sdr.{MT48LC16M16A2, SdramTimings, SdramInterface} + +case class SdramConfig( + CAS: Int = 3, + addressWidth: Int = 32, + burstLen: Int = 16, + busDataWidth: Int = 32, + idWidth: Int = 4 +) { + val busByteSize = busDataWidth / 8 + val burstLenWidth = 8 // AXI4 burst length width // log2Up(burstLen) + val burstByteSize = burstLen * busByteSize + val fullStrbBits = scala.math.pow(2, busByteSize).toInt - 1 // all bits valid + val bufDepth = burstLen * 4 + + require( + busDataWidth % 8 == 0, + s"${busDataWidth} % 8 == 0 bus data width assertaion failed" + ) + require(burstLen <= 256, s"$burstLen < 256 burst lenth assertion failed") + + val axiConfig = Axi4Config( + addressWidth = addressWidth, + dataWidth = busDataWidth, + idWidth = idWidth, + useId = true, + useQos = false, + useRegion = false, + useLock = false, + useCache = false, + useProt = false + ) +} + +class SdramController(l: SdramLayout, t: SdramTimings, c: SdramConfig) + extends Component { + require(c.burstLen >= 4, "burst length at least 4") + require( + c.burstLen <= l.columnSize, + "burst length should be less than column size" + ) + require( + c.busDataWidth % l.dataWidth == 0, + "AXI bus data width is not divisible by SDRAM data width" + ) + + val DATA_WIDTH_MULIPLER = c.busDataWidth / l.dataWidth + val dataWidthMultiplerSet = Set(1, 2, 4, 8, 16, 32, 64, 128, 256) + require( + dataWidthMultiplerSet(DATA_WIDTH_MULIPLER), + "DATA_WIDTH_MULIPLER is not valid" + ) + + val CMD_UNSELECTED = B"4'b1000" + val CMD_NOP = B"4'b0111" + val CMD_ACTIVE = B"4'b0011" + val CMD_READ = B"4'b0101" + val CMD_WRITE = B"4'b0100" + val CMD_BURST_TERMINATE = B"4'b0110" + val CMD_PRECHARGE = B"4'b0010" + val CMD_REFRESH = B"4'b0001" + val CMD_LOAD_MODE_REG = B"4'b0000" + + val DQM_ALL_VALID = B(0, l.bytePerWord bits) // 2'b00 + val DQM_ALL_INVALID = ~DQM_ALL_VALID // High means invalid + val DQM_READ_DELAY_CYCLES = 2 + + val MODE_VALUE = + U"6'b000_0_00" @@ U(c.CAS, 3 bits) @@ U"4'b0_111" // sequential full page + val ALL_BANK_ADDR = 1 << 10 + + val io = new Bundle { + val axi = slave(Axi4(c.axiConfig)) + val sdram = master(SdramInterface(l)) // setAsReg + val initDone = out Bool + + axi.addAttribute(name = "IOB", value = "TRUE") + sdram.addAttribute(name = "IOB", value = "TRUE") + initDone.addAttribute(name = "IOB", value = "TRUE") + } + val awFifo = StreamFifo(Axi4Aw(c.axiConfig), c.bufDepth) + val wFifo = StreamFifo(Axi4W(c.axiConfig), c.bufDepth) + val bFifo = StreamFifo(Axi4B(c.axiConfig), c.bufDepth) + val arFifo = StreamFifo(Axi4Ar(c.axiConfig), c.bufDepth) + val rFifo = StreamFifo(Axi4R(c.axiConfig), c.bufDepth) + awFifo.io.push << io.axi.aw + wFifo.io.push << io.axi.w + io.axi.b << bFifo.io.pop + arFifo.io.push << io.axi.ar + io.axi.r << rFifo.io.pop + + val commandReg = Reg(Bits(4 bits)) init (0) + val addressReg = Reg(UInt(l.chipAddressWidth bits)) init (0) + val bankAddrReg = Reg(UInt(l.bankWidth bits)) init (0) + val burstLenReg = Reg(UInt(c.burstLenWidth bits)) init (0) + val columnAddrReg = Reg(UInt(l.columnWidth bits)) init (0) + val busReadDataReg = Reg(Bits(c.busDataWidth bits)) init (0) + val busReadDataVldReg = Reg(Bool) init (False) + val busReadDataLastReg = Reg(Bool) init (False) + val opIdReg = Reg(UInt(c.idWidth bits)) init (0) + val strobeReg = Reg(Bits(c.busByteSize bits)) init (0) + val busWriteDataReg = Reg(Bits(c.busDataWidth bits)) init (0) + val busDataShiftCnt = + Reg(UInt((log2Up(DATA_WIDTH_MULIPLER) + 1) bits)) init (0) + + val writeMask = strobeReg((l.bytePerWord - 1) downto 0) + val busWrite = busWriteDataReg((l.dataWidth - 1) downto 0) + val mask = Bits(l.bytePerWord bits) + + awFifo.io.pop.ready := False + wFifo.io.pop.ready := False + bFifo.io.push.valid := False + arFifo.io.pop.ready := False + + bFifo.io.push.payload.id := opIdReg + bFifo.io.push.payload.setOKAY() + rFifo.io.push.payload.data := busReadDataReg + rFifo.io.push.payload.id := opIdReg + rFifo.io.push.payload.last := busReadDataLastReg + rFifo.io.push.payload.setOKAY() + rFifo.io.push.valid := busReadDataVldReg + + io.sdram.BA := bankAddrReg.asBits + io.sdram.ADDR := addressReg.asBits + io.sdram.DQM := mask + io.sdram.CKE := True + io.sdram.CSn := commandReg(3) + io.sdram.RASn := commandReg(2) + io.sdram.CASn := commandReg(1) + io.sdram.WEn := commandReg(0) + io.sdram.DQ.write := busWrite //wFifo.io.pop.payload.data + io.initDone := False + + commandReg := CMD_NOP + busReadDataLastReg := False + + assert( + assertion = (columnAddrReg < l.columnSize - c.burstLen), + message = "invalid column address and burst length", + severity = ERROR + ) + assert( + assertion = + awFifo.io.pop.payload.isINCR() && arFifo.io.pop.payload.isINCR(), + message = "only burst type INCR allowed", + severity = ERROR + ) + assert( + assertion = awFifo.io.pop.payload.len < (256 >> DATA_WIDTH_MULIPLER), + message = s"burst length should be less than 256/${DATA_WIDTH_MULIPLER}", + severity = ERROR + ) + + val clkFrequancy = ClockDomain.current.frequency.getValue + def timeToCycles(time: TimeNumber): BigInt = + (clkFrequancy * time).setScale(0, RoundingMode.UP).toBigInt + + def cycleCounter(cycleMax: BigInt) = new Area { + val counter = Reg(UInt(log2Up(cycleMax) bits)) init (0) + val busy = counter =/= 0 + if (cycleMax > 1) { + when(busy) { + counter := counter - 1 + } + } + def setCycles(cycles: BigInt) = { + assert( + cycles <= cycleMax && cycles > 0, + s"invalid counter cycle=${cycles}, cycleMax=${cycleMax}" + ) + counter := cycles - 1 + } + def setCycles(cycles: UInt) = { + assert( + cycles <= cycleMax && cycles > 0, + s"invalid counter cycle=${cycles}, cycleMax=${cycleMax}" + ) + counter := (cycles - 1).resized + } + def setTime(time: TimeNumber) = setCycles( + timeToCycles(time).max(1) + ) // Minimum 1 cycles + } + def timeCounter(timeMax: TimeNumber) = cycleCounter(timeToCycles(timeMax)) + + val initCounter = timeCounter(t.tPOW) + val stateCounter = timeCounter( + t.tRFC + ) // tRFC is the largest delay except tPOW and tREF + val refreshCounter = CounterFreeRun(timeToCycles(t.tREF / (1 << l.rowWidth))) + + val initPeriod = Bool + val refreshReqReg = Reg(Bool) init (False) + val preReqIsWriteReg = Reg(Bool) init (False) + val readReq = + arFifo.io.pop.valid && arFifo.io.pop.payload.len <= rFifo.io.availability + val writeReq = + awFifo.io.pop.valid && awFifo.io.pop.payload.len <= wFifo.io.occupancy && bFifo.io.availability > 0 + + val initFsm = new StateMachine { + val INIT_WAIT: State = new State with EntryPoint { + onEntry { + initCounter.setTime(t.tPOW) + } whenIsActive { + when(!initCounter.busy) { + goto(INIT_PRECHARGE) + } + } + } + + val INIT_PRECHARGE: State = new State { + onEntry { + addressReg := ALL_BANK_ADDR + commandReg := CMD_PRECHARGE + stateCounter.setTime(t.tRP) + } whenIsActive { + when(!stateCounter.busy) { + goto(INIT_REFRESH_1) + } + } + } + + val INIT_REFRESH_1: State = new State { + onEntry { + commandReg := CMD_REFRESH + stateCounter.setTime(t.tRFC) + } whenIsActive { + when(!stateCounter.busy) { + goto(INIT_REFRESH_2) + } + } + } + + val INIT_REFRESH_2: State = new State { + onEntry { + commandReg := CMD_REFRESH + stateCounter.setTime(t.tRFC) + } whenIsActive { + when(!stateCounter.busy) { + goto(INIT_LOAD_MODE_REG) + } + } + } + + val INIT_LOAD_MODE_REG: State = new State { + onEntry { + addressReg := MODE_VALUE + commandReg := CMD_LOAD_MODE_REG + stateCounter.setCycles(t.cMRD) + } whenIsActive { + when(!stateCounter.busy) { + exit() + } + } + } + } + + val refreshFsm = new StateMachine { + val REFRESH_PRECHARGE: State = new State with EntryPoint { + onEntry { + addressReg := ALL_BANK_ADDR + commandReg := CMD_REFRESH + stateCounter.setTime(t.tRP) + } whenIsActive { + when(!stateCounter.busy) { + goto(REFRESH) + } + } + } + + val REFRESH: State = new State { + onEntry { + commandReg := CMD_PRECHARGE + stateCounter.setTime(t.tRFC) + } whenIsActive { + when(!stateCounter.busy) { + exit() + } + } onExit { + refreshReqReg := False + } + } + } + + val writeFsm = new StateMachine { + val ACTIVE_WRITE: State = new State with EntryPoint { + onEntry { + commandReg := CMD_ACTIVE + bankAddrReg := awFifo.io.pop.payload + .addr(l.wordAddressWidth - l.bankWidth - 1, l.bankWidth bits) + addressReg := awFifo.io.pop.payload.addr( + (l.rowWidth + l.columnWidth - 1) downto l.columnWidth + ) // Row address + columnAddrReg := awFifo.io.pop.payload + .addr((l.columnWidth - 1) downto 0) + .resized // Colume address + opIdReg := awFifo.io.pop.payload.id + + burstLenReg := (awFifo.io.pop.payload.len << log2Up( + DATA_WIDTH_MULIPLER + )).resized + stateCounter.setTime(t.tRCD) + + awFifo.io.pop.ready := True // awFifo.io.pop.valid must be true here + } whenIsActive { + when(!stateCounter.busy) { + goto(BURST_WRITE) + } + } + } + + val BURST_WRITE: State = new State { + onEntry { + addressReg := columnAddrReg.resized + commandReg := CMD_WRITE + stateCounter.setCycles(burstLenReg) + + strobeReg := wFifo.io.pop.payload.strb + busWriteDataReg := wFifo.io.pop.payload.data + wFifo.io.pop.ready := True // wFifo.io.pop.valid must be true here + busDataShiftCnt := DATA_WIDTH_MULIPLER - 1 + } whenIsActive { + if (DATA_WIDTH_MULIPLER > 1) { + strobeReg := (strobeReg >> l.bytePerWord).resized + busWriteDataReg := (busWriteDataReg >> l.dataWidth).resized + when(busDataShiftCnt > 0) { + busDataShiftCnt := busDataShiftCnt - 1 + } otherwise { + strobeReg := wFifo.io.pop.payload.strb + busWriteDataReg := wFifo.io.pop.payload.data + wFifo.io.pop.ready := True // wFifo.io.pop.valid must be true here + busDataShiftCnt := DATA_WIDTH_MULIPLER - 1 + } + } else { + busWriteDataReg := wFifo.io.pop.payload.data + wFifo.io.pop.ready := stateCounter.busy // wFifo.io.pop.valid must be true here + } + + when(!stateCounter.busy) { + goto(TERM_WRITE) + } + } + } + + val TERM_WRITE: State = new State { + onEntry { + commandReg := CMD_BURST_TERMINATE + } whenIsActive { + exit() // Must be one cycle, because AXI4 write response will be sent in this cycle right after burst write finish + } onExit { + preReqIsWriteReg := True + bFifo.io.push.valid := True // bFifo.io.push.ready must be true here + } + } + } + + val readFsm = new StateMachine { + val ACTIVE: State = new State with EntryPoint { + onEntry { + commandReg := CMD_ACTIVE + bankAddrReg := arFifo.io.pop.payload + .addr(l.wordAddressWidth - l.bankWidth - 1, l.bankWidth bits) + addressReg := arFifo.io.pop.payload.addr( + (l.rowWidth + l.columnWidth - 1) downto l.columnWidth + ) // Row address + columnAddrReg := arFifo.io.pop.payload + .addr((l.columnWidth - 1) downto 0) + .resized // Colume address + opIdReg := arFifo.io.pop.payload.id + + burstLenReg := (arFifo.io.pop.payload.len << log2Up( + DATA_WIDTH_MULIPLER + )).resized + stateCounter.setTime(t.tRCD) + + arFifo.io.pop.ready := True // arFifo.io.pop.valid must be true here + } whenIsActive { + when(!stateCounter.busy) { + goto(SEND_READ_CMD) + } + } + } + + val SEND_READ_CMD: State = new State { + onEntry { + addressReg := columnAddrReg.resized + commandReg := CMD_READ + stateCounter.setCycles(c.CAS) + } whenIsActive { + when(!stateCounter.busy) { + goto(BURST_READ) + } + } + } + + val BURST_READ: State = new State { + onEntry { + stateCounter.setCycles(burstLenReg) + } whenIsActive { + when(stateCounter.counter === c.CAS) { + commandReg := CMD_BURST_TERMINATE + } elsewhen (!stateCounter.busy) { + exit() + } + } onExit { + preReqIsWriteReg := False + busReadDataLastReg := True + } + } + } + + val fsm = new StateMachine { + val INIT: State = new StateFsm(initFsm) with EntryPoint { + whenCompleted { + goto(IDLE) + } onExit { + io.initDone := True + } + } + + val IDLE: State = new State { + whenIsActive { + when(refreshReqReg) { + goto(REFRESH) + } elsewhen (readReq && writeReq) { + when(preReqIsWriteReg) { + goto(READ) + } otherwise { + goto(WRITE) + } + } elsewhen (writeReq && !readReq) { + goto(WRITE) + } elsewhen (readReq && !writeReq) { + goto(READ) + } + } + } + + val REFRESH: State = new StateFsm(refreshFsm) { + whenCompleted { + goto(IDLE) + } + } + + val WRITE: State = new StateFsm(writeFsm) { + whenCompleted { + goto(PRECHARGE) + } + } + + val READ: State = new StateFsm(readFsm) { + whenCompleted { + goto(PRECHARGE) + } + } + + val PRECHARGE: State = new State { + onEntry { + commandReg := CMD_PRECHARGE + } whenIsActive { + goto(IDLE) + } + } + } + + // assert( + // assertion = writeFsm.isEntering(writeFsm.TERM_WRITE) && wFifo.io.pop.payload.last, + // message = "write burst finish requires AXI AWLAST is true", + // severity = ERROR + // ) + + initPeriod := fsm.isActive(fsm.INIT) + + when(writeFsm.isActive(writeFsm.BURST_WRITE)) { + mask := DQM_ALL_VALID | ~writeMask + } elsewhen (fsm.isActive(fsm.READ)) { + mask := DQM_ALL_VALID + } otherwise { + mask := DQM_ALL_INVALID + } + + // Handle SDRAM read + val readArea = new ClockingArea( + clockDomain = ClockDomain( + clock = ClockDomain.current.clock, + reset = ClockDomain.current.reset, + config = ClockDomainConfig(clockEdge = FALLING) + ) + ) { + val readReg = RegNextWhen(io.sdram.DQ.read, io.sdram.DQ.writeEnable === 0) + } + + val startBurstReadReg = RegNext(readFsm.isEntering(readFsm.BURST_READ)) + if (DATA_WIDTH_MULIPLER > 1) { + when(startBurstReadReg) { + busDataShiftCnt := DATA_WIDTH_MULIPLER - 1 + } + + busReadDataVldReg := False + when(readFsm.isActive(readFsm.BURST_READ)) { + // Little Endien + busReadDataReg := readArea.readReg ## busReadDataReg( + (c.busDataWidth - 1) downto l.dataWidth + ) + // Big Endien + // busReadDataReg := busReadDataReg( + // (c.busDataWidth - l.dataWidth - 1) downto 0 + // ) ## readArea.readReg + when(busDataShiftCnt > 0) { + busDataShiftCnt := busDataShiftCnt - 1 + } otherwise { + busReadDataVldReg := True + busDataShiftCnt := DATA_WIDTH_MULIPLER - 1 + } + } + } else { + busReadDataReg := readArea.readReg + busReadDataVldReg := readFsm.isActive(readFsm.BURST_READ) + } + + when(!initPeriod && refreshCounter.willOverflow) { + refreshReqReg := True + } + + when(writeFsm.isActive(writeFsm.BURST_WRITE)) { + io.sdram.DQ.writeEnable.setAll() + } otherwise { + io.sdram.DQ.writeEnable := 0 + } +} + +object SdramController { + def main(args: Array[String]): Unit = { + val device = MT48LC16M16A2 + SpinalConfig(defaultClockDomainFrequency = FixedFrequency(100 MHz)) + .generateVerilog( + new SdramController( + device.layout, + device.timingGrade7, + SdramConfig() + ) + ) + } +}
diff --git a/spinal-cocotb/SpinalNet/test/src/python/common/timescale.v b/spinal-cocotb/SpinalNet/test/src/python/common/timescale.v new file mode 100644 index 0000000..e66f979 --- /dev/null +++ b/spinal-cocotb/SpinalNet/test/src/python/common/timescale.v
@@ -0,0 +1 @@ +`timescale 1ns/1ps
diff --git a/spinal-cocotb/SpinalNet/test/src/python/sdram_controller/Makefile b/spinal-cocotb/SpinalNet/test/src/python/sdram_controller/Makefile new file mode 100644 index 0000000..0993202 --- /dev/null +++ b/spinal-cocotb/SpinalNet/test/src/python/sdram_controller/Makefile
@@ -0,0 +1,15 @@ +#SIM ?= icarus + + +# ../../../../../simWorkspace/SdramControllerTb/rtl/SdramControllerTb.v +VERILOG_SOURCES += ../../../../../simWorkspace/SdramController/rtl/SdramController.v ../../verilog/tb_sdram_controller.v ../../verilog/mt48lc16m16a2.v ../common/timescale.v +TOPLEVEL=tb_sdram_controller +MODULE=SdramControllerTest + +# Python path to cocotblib +export PYTHONPATH := .. +export COCOTB_RESOLVE_X := ZEROS +#export COCOTB_LOG_LEVEL := DEBUG +#export COCOTB_SCHEDULER_DEBUG := 1 +include $(shell cocotb-config --makefiles)/Makefile.sim +#include ../common/Makefile.sim
diff --git a/spinal-cocotb/SpinalNet/test/src/python/sdram_controller/SdramControllerTest.py b/spinal-cocotb/SpinalNet/test/src/python/sdram_controller/SdramControllerTest.py new file mode 100644 index 0000000..9f08633 --- /dev/null +++ b/spinal-cocotb/SpinalNet/test/src/python/sdram_controller/SdramControllerTest.py
@@ -0,0 +1,107 @@ +import cocotb +from cocotb import utils +from cocotb.triggers import Timer, RisingEdge +from cocotblib.misc import simulationSpeedPrinter + +async def ClockDomainAsyncResetCustom(clk, reset): + if reset: + reset <= 1 + clk <= 0 + await Timer(100000) + if reset: + reset <= 0 + while True: + clk <= 0 + await Timer(3750) + clk <= 1 + await Timer(3750) + +async def waitUntil(clk, cond): + while True: + if cond(): + break + await RisingEdge(clk) + +@cocotb.test() +async def testFunc(dut): + cocotb.fork(ClockDomainAsyncResetCustom(dut.clk, dut.reset)) + cocotb.fork(simulationSpeedPrinter(dut.clk)) + + dut.io_axi_aw_valid <= False + dut.io_axi_w_valid <= False + dut.io_axi_b_ready <= False + dut.io_axi_ar_valid <= False + dut.io_axi_r_ready <= False + + await waitUntil(dut.clk, lambda: int(dut.io_initDone) == 1) + print("init done at: {}".format(utils.get_sim_time(units='ns'))) + dut.io_axi_b_ready <= True + + busDataWidth = 32 + sdramDQWidth = 16 + dataWidthMultipler = int(busDataWidth / sdramDQWidth) # AXI burst size in bytes + strobe = int(pow(2, pow(2, dataWidthMultipler)) - 1) + burstLen = 4 + rangeMax = 16 + print("bus width = {}, SDRAM DQ width = {}, strobe = {}, burst length = {}".format( + busDataWidth, sdramDQWidth, strobe, burstLen + )) + + for i in range(rangeMax): + dut.io_axi_aw_valid <= True + dut.io_axi_aw_payload_addr <= i * burstLen * dataWidthMultipler + dut.io_axi_aw_payload_id <= i + dut.io_axi_aw_payload_len <= burstLen + dut.io_axi_aw_payload_size <= dataWidthMultipler + dut.io_axi_aw_payload_burst <= 1 # INCR + await RisingEdge(dut.clk) + await waitUntil(dut.clk, lambda: (int(dut.io_axi_aw_valid) == 1 and int(dut.io_axi_aw_ready) == 1)) + dut.io_axi_aw_valid <= False + + for d in range(burstLen): + dut.io_axi_w_valid <= True + dut.io_axi_w_payload_data <= i * burstLen + d + dut.io_axi_w_payload_strb <= strobe + dut.io_axi_w_payload_last <= ((d + 1) % burstLen == 0) + await RisingEdge(dut.clk) + await waitUntil(dut.clk, lambda: (int(dut.io_axi_w_valid) == 1 and int(dut.io_axi_w_ready) == 1)) + dut.io_axi_w_valid <= False + + print("write: addr={}, data={}, id={}, last={}".format( + i * burstLen + d, + int(dut.io_axi_w_payload_data), + int(dut.io_axi_aw_payload_id), + int(dut.io_axi_w_payload_last) + )) + + await waitUntil(dut.clk, lambda: (int(dut.io_axi_b_valid) == 1 and int(dut.io_axi_b_ready) == 1)) + + dut.io_axi_b_ready <= False + dut.io_axi_r_ready <= True + + for i in range(rangeMax): + dut.io_axi_ar_valid <= True + dut.io_axi_ar_payload_addr <= i * burstLen * dataWidthMultipler + dut.io_axi_ar_payload_id <= i + dut.io_axi_ar_payload_len <= burstLen + dut.io_axi_ar_payload_size <= dataWidthMultipler + dut.io_axi_ar_payload_burst <= 1 # INCR + await RisingEdge(dut.clk) + await waitUntil(dut.clk, lambda: (int(dut.io_axi_ar_valid) == 1 and int(dut.io_axi_ar_ready) == 1)) + dut.io_axi_ar_valid <= False + + for d in range(burstLen): + await RisingEdge(dut.clk) + await waitUntil(dut.clk, lambda: (int(dut.io_axi_r_valid) == 1 and int(dut.io_axi_r_ready) == 1)) + + print("read: addr={}, data={}, id={}, last={}".format( + (i * burstLen + d), + int(dut.io_axi_r_payload_data), + int(dut.io_axi_r_payload_id), + int(dut.io_axi_r_payload_last) + )) + assert int(dut.io_axi_r_payload_data) == (i * burstLen + d), "read data not match" + + dut.io_axi_r_ready <= False + print("finished at: {}".format(utils.get_sim_time(units='ns'))) + await RisingEdge(dut.clk)
diff --git a/spinal-cocotb/SpinalNet/test/src/scala/dma/DmaControllerTest.scala b/spinal-cocotb/SpinalNet/test/src/scala/dma/DmaControllerTest.scala new file mode 100644 index 0000000..a2fed7f --- /dev/null +++ b/spinal-cocotb/SpinalNet/test/src/scala/dma/DmaControllerTest.scala
@@ -0,0 +1,15 @@ + +package dma + +import org.scalatest.funsuite.AnyFunSuite + +import spinal.core._ +import spinal.core.sim._ +import spinal.lib._ +import spinal.sim._ + +class DmaControllerTest extends AnyFunSuite { + test("dma controller test") { + DmaControllerSim.main(null) + } +} \ No newline at end of file
diff --git a/spinal-cocotb/SpinalNet/test/src/scala/sdram/SdramControllerTest.scala b/spinal-cocotb/SpinalNet/test/src/scala/sdram/SdramControllerTest.scala new file mode 100644 index 0000000..5a43781 --- /dev/null +++ b/spinal-cocotb/SpinalNet/test/src/scala/sdram/SdramControllerTest.scala
@@ -0,0 +1,114 @@ +package sdram + +import org.scalatest.funsuite.AnyFunSuite + +import spinal.core._ +import spinal.core.sim._ +import spinal.lib._ +import spinal.lib.com.eth._ +import spinal.sim._ +import spinal.lib.memory.sdram.SdramLayout +import spinal.lib.memory.sdram.sdr.{MT48LC16M16A2, SdramTimings, SdramInterface} + +import testutils.CocotbRunner + +case class mt48lc16m16a2(l: SdramLayout) extends BlackBox { + val io = new Bundle { + val sdram = slave(SdramInterface(l)).setName("") + } + + noIoPrefix() + addRTLPath("./SpinalNet/test/src/verilog/mt48lc16m16a2.v") +} + +/* +case class SdramModelPlus(l: SdramLayout) extends BlackBox { + val io = new Bundle { + val Dq = inout Bits(l.dataWidth bits) + val Addr = in Bits(l.chipAddressWidth bits) + val Ba = in Bits(l.bankWidth bits) + val Clk = in Bool + val Cke = in Bool + val Ras_n = in Bool + val Cas_n = in Bool + val Cs_n = in Bool + val We_n = in Bool + val Dqm = in Bits(l.bytePerWord bits) + val Debug = in Bool + } + mapCurrentClockDomain(io.Clk) + noIoPrefix() + addGeneric("addr_bits", l.chipAddressWidth) + addGeneric("data_bits", l.dataWidth) + addGeneric("col_bits", l.columnWidth) + addRTLPath("./SpinalNet/test/src/verilog/SdramModelPlus.v") +} + +class SdramControllerTb(l: SdramLayout, t: SdramTimings, c: SdramConfig) extends Component { + val io = new Bundle { + val bus = slave(SdramBus(l, c)) + } + + val controller = new SdramController(l, t, c) + controller.io.bus <> io.bus + + val sdramDevice = SdramModelPlus(l) + sdramDevice.io.Addr := controller.io.sdram.ADDR + sdramDevice.io.Ba := controller.io.sdram.BA + sdramDevice.io.Cke := controller.io.sdram.CKE + sdramDevice.io.Ras_n := controller.io.sdram.RASn + sdramDevice.io.Cas_n := controller.io.sdram.CASn + sdramDevice.io.Cs_n := controller.io.sdram.CSn + sdramDevice.io.We_n := controller.io.sdram.WEn + sdramDevice.io.Dqm := controller.io.sdram.DQM + sdramDevice.io.Debug := True + when (controller.io.sdram.DQ.writeEnable === 1) { + sdramDevice.io.Dq := controller.io.sdram.DQ.write + } + controller.io.sdram.DQ.read := sdramDevice.io.Dq + + // val sdramDevice = mt48lc16m16a2(l) + // sdramDevice.io.sdram <> controller.io.sdram +} +*/ + +class SdramControllerTest extends AnyFunSuite { + val device = MT48LC16M16A2 + val l = device.layout + val t = device.timingGrade7 + val c = SdramConfig() + + test("sdram test") { + val compiled = SimConfig + .withWave + .withConfig(SpinalConfig(defaultClockDomainFrequency = FixedFrequency(200 MHz))) + .compile(new SdramController(l, t, c)) + assert(CocotbRunner("./SpinalNet/test/src/python/sdram_controller"), "Simulation faild") + println("SUCCESS") + } + +/* + def simTest(dut: SdramControllerTb) { + SimTimeout(1000) + dut.clockDomain.forkStimulus(2) + + dut.io.bus.cmd.valid #= true + dut.io.bus.cmd.address #= 0 + dut.io.bus.cmd.write #= true + dut.io.bus.cmd.data #= 127 + dut.io.bus.cmd.burstLen #= 1 + dut.io.bus.cmd.mask #= 3 + dut.io.bus.cmd.opId #= 1 + dut.io.bus.cmd.last #= true + dut.io.bus.rsp.ready #= false + } + + test("sdram test") { + SimConfig + .withWave + .withConfig(SpinalConfig(defaultClockDomainFrequency = FixedFrequency(100 MHz))) + .compile(new SdramControllerTb(l, t, c)) + .doSim(simTest(_)) + } +*/ +}
diff --git a/spinal-cocotb/SpinalNet/test/src/scala/testutils/CocotbRunner.scala b/spinal-cocotb/SpinalNet/test/src/scala/testutils/CocotbRunner.scala new file mode 100644 index 0000000..1e76b30 --- /dev/null +++ b/spinal-cocotb/SpinalNet/test/src/scala/testutils/CocotbRunner.scala
@@ -0,0 +1,50 @@ +package testutils + +import scala.sys.process.{Process, ProcessIO} + +object CocotbRunner { + def apply(path: String): Boolean = { + + doCmd(Seq(s"cd $path", "rm -f results.xml", "make")) + getCocotbPass(path) + } + + def doCmd(cmds: Seq[String]): Unit = { + var out, err: String = null + val io = new ProcessIO( + stdin => { + for (cmd <- cmds) + stdin.write((cmd + "\n").getBytes) + stdin.close() + }, + stdout => { + out = scala.io.Source + .fromInputStream(stdout) + .getLines + .foldLeft("")(_ + "\n" + _) + stdout.close() + }, + stderr => { + err = scala.io.Source + .fromInputStream(stderr) + .getLines + .foldLeft("")(_ + "\n" + _) + stderr.close() + } + ) + val proc = Process("sh").run(io) + proc.exitValue() + println(out) + println(err) + } + + def getCocotbPass(path: String): Boolean = { + import scala.io.Source + for (line <- Source.fromFile(path + "/results.xml").getLines()) { + if (line.contains("failure") || line.contains("skipped")) { + return false + } + } + return true + } +}
diff --git a/spinal-cocotb/SpinalNet/test/src/verilog/mt48lc16m16a2.v b/spinal-cocotb/SpinalNet/test/src/verilog/mt48lc16m16a2.v new file mode 100644 index 0000000..dca98d8 --- /dev/null +++ b/spinal-cocotb/SpinalNet/test/src/verilog/mt48lc16m16a2.v
@@ -0,0 +1,1072 @@ +/************************************************************************** +* +* File Name: MT48LC16M16A2.V +* Version: 2.1 +* Date: June 6th, 2002 +* Model: BUS Functional +* Simulator: Model Technology +* +* Dependencies: None +* +* Email: modelsupport@micron.com +* Company: Micron Technology, Inc. +* Model: MT48LC16M16A2 (4Meg x 16 x 4 Banks) +* +* Description: Micron 256Mb SDRAM Verilog model +* +* Limitation: - Doesn't check for 8192 cycle refresh +* +* Note: - Set simulator resolution to "ps" accuracy +* - Set Debug = 0 to disable $display messages +* +* Disclaimer: THESE DESIGNS ARE PROVIDED "AS IS" WITH NO WARRANTY +* WHATSOEVER AND MICRON SPECIFICALLY DISCLAIMS ANY +* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR +* A PARTICULAR PURPOSE, OR AGAINST INFRINGEMENT. +* +* Copyright � 2001 Micron Semiconductor Products, Inc. +* All rights researved +* +* Rev Author Date Changes +* --- -------------------------- --------------------------------------- +* 2.1 SH 06/06/2002 - Typo in bank multiplex +* Micron Technology Inc. +* +* 2.0 SH 04/30/2002 - Second release +* Micron Technology Inc. +* +**************************************************************************/ + +`timescale 1ns / 1ps + +module mt48lc16m16a2 (Dq, Addr, Ba, Clk, Cke, Cs_n, Ras_n, Cas_n, We_n, Dqm); + + parameter addr_bits = 13; + parameter data_bits = 16; + parameter col_bits = 9; + parameter mem_sizes = 100000; + + inout [data_bits - 1 : 0] Dq; + input [addr_bits - 1 : 0] Addr; + input [1 : 0] Ba; + input Clk; + input Cke; + input Cs_n; + input Ras_n; + input Cas_n; + input We_n; + input [1 : 0] Dqm; + + reg [data_bits - 1 : 0] Bank0 [0 : mem_sizes]; + reg [data_bits - 1 : 0] Bank1 [0 : mem_sizes]; + reg [data_bits - 1 : 0] Bank2 [0 : mem_sizes]; + reg [data_bits - 1 : 0] Bank3 [0 : mem_sizes]; + + reg [1 : 0] Bank_addr [0 : 3]; // Bank Address Pipeline + reg [col_bits - 1 : 0] Col_addr [0 : 3]; // Column Address Pipeline + reg [3 : 0] Command [0 : 3]; // Command Operation Pipeline + reg [1 : 0] Dqm_reg0, Dqm_reg1; // DQM Operation Pipeline + reg [addr_bits - 1 : 0] B0_row_addr, B1_row_addr, B2_row_addr, B3_row_addr; + + reg [addr_bits - 1 : 0] Mode_reg; + reg [data_bits - 1 : 0] Dq_reg, Dq_dqm; + reg [col_bits - 1 : 0] Col_temp, Burst_counter; + + reg Act_b0, Act_b1, Act_b2, Act_b3; // Bank Activate + reg Pc_b0, Pc_b1, Pc_b2, Pc_b3; // Bank Precharge + + reg [1 : 0] Bank_precharge [0 : 3]; // Precharge Command + reg A10_precharge [0 : 3]; // Addr[10] = 1 (All banks) + reg Auto_precharge [0 : 3]; // RW Auto Precharge (Bank) + reg Read_precharge [0 : 3]; // R Auto Precharge + reg Write_precharge [0 : 3]; // W Auto Precharge + reg RW_interrupt_read [0 : 3]; // RW Interrupt Read with Auto Precharge + reg RW_interrupt_write [0 : 3]; // RW Interrupt Write with Auto Precharge + reg [1 : 0] RW_interrupt_bank; // RW Interrupt Bank + integer RW_interrupt_counter [0 : 3]; // RW Interrupt Counter + integer Count_precharge [0 : 3]; // RW Auto Precharge Counter + + reg Data_in_enable; + reg Data_out_enable; + + reg [1 : 0] Bank, Prev_bank; + reg [addr_bits - 1 : 0] Row; + reg [col_bits - 1 : 0] Col, Col_brst; + + // Internal system clock + reg CkeZ, Sys_clk; + + // Commands Decode + wire Active_enable = ~Cs_n & ~Ras_n & Cas_n & We_n; + wire Aref_enable = ~Cs_n & ~Ras_n & ~Cas_n & We_n; + wire Burst_term = ~Cs_n & Ras_n & Cas_n & ~We_n; + wire Mode_reg_enable = ~Cs_n & ~Ras_n & ~Cas_n & ~We_n; + wire Prech_enable = ~Cs_n & ~Ras_n & Cas_n & ~We_n; + wire Read_enable = ~Cs_n & Ras_n & ~Cas_n & We_n; + wire Write_enable = ~Cs_n & Ras_n & ~Cas_n & ~We_n; + + // Burst Length Decode + wire Burst_length_1 = ~Mode_reg[2] & ~Mode_reg[1] & ~Mode_reg[0]; + wire Burst_length_2 = ~Mode_reg[2] & ~Mode_reg[1] & Mode_reg[0]; + wire Burst_length_4 = ~Mode_reg[2] & Mode_reg[1] & ~Mode_reg[0]; + wire Burst_length_8 = ~Mode_reg[2] & Mode_reg[1] & Mode_reg[0]; + wire Burst_length_f = Mode_reg[2] & Mode_reg[1] & Mode_reg[0]; + + // CAS Latency Decode + wire Cas_latency_2 = ~Mode_reg[6] & Mode_reg[5] & ~Mode_reg[4]; + wire Cas_latency_3 = ~Mode_reg[6] & Mode_reg[5] & Mode_reg[4]; + + // Write Burst Mode + wire Write_burst_mode = Mode_reg[9]; + + wire Debug = 1'b0; // Debug messages : 1 = On + wire Dq_chk = Sys_clk & Data_in_enable; // Check setup/hold time for DQ + + assign Dq = Dq_reg; // DQ buffer + + // Commands Operation + `define ACT 0 + `define NOP 1 + `define READ 2 + `define WRITE 3 + `define PRECH 4 + `define A_REF 5 + `define BST 6 + `define LMR 7 + + // Timing Parameters for -7E PC133 CL2 + parameter tAC = 5.4; + parameter tHZ = 5.4; + parameter tOH = 3.0; + parameter tMRD = 2.0; // 2 Clk Cycles + parameter tRAS = 37.0; + parameter tRC = 60.0; + parameter tRCD = 15.0; + parameter tRFC = 66.0; + parameter tRP = 15.0; + parameter tRRD = 14.0; + parameter tWRa = 7.0; // A2 Version - Auto precharge mode (1 Clk + 7 ns) + parameter tWRm = 14.0; // A2 Version - Manual precharge mode (14 ns) + + // Timing Check variable + time MRD_chk; + time WR_chkm [0 : 3]; + time RFC_chk, RRD_chk; + time RC_chk0, RC_chk1, RC_chk2, RC_chk3; + time RAS_chk0, RAS_chk1, RAS_chk2, RAS_chk3; + time RCD_chk0, RCD_chk1, RCD_chk2, RCD_chk3; + time RP_chk0, RP_chk1, RP_chk2, RP_chk3; + + initial begin + Dq_reg = {data_bits{1'bz}}; + Data_in_enable = 0; Data_out_enable = 0; + Act_b0 = 1; Act_b1 = 1; Act_b2 = 1; Act_b3 = 1; + Pc_b0 = 0; Pc_b1 = 0; Pc_b2 = 0; Pc_b3 = 0; + WR_chkm[0] = 0; WR_chkm[1] = 0; WR_chkm[2] = 0; WR_chkm[3] = 0; + RW_interrupt_read[0] = 0; RW_interrupt_read[1] = 0; RW_interrupt_read[2] = 0; RW_interrupt_read[3] = 0; + RW_interrupt_write[0] = 0; RW_interrupt_write[1] = 0; RW_interrupt_write[2] = 0; RW_interrupt_write[3] = 0; + MRD_chk = 0; RFC_chk = 0; RRD_chk = 0; + RAS_chk0 = 0; RAS_chk1 = 0; RAS_chk2 = 0; RAS_chk3 = 0; + RCD_chk0 = 0; RCD_chk1 = 0; RCD_chk2 = 0; RCD_chk3 = 0; + RC_chk0 = 0; RC_chk1 = 0; RC_chk2 = 0; RC_chk3 = 0; + RP_chk0 = 0; RP_chk1 = 0; RP_chk2 = 0; RP_chk3 = 0; + $timeformat (-9, 1, " ns", 12); + end + + // System clock generator + always begin + @ (posedge Clk) begin + Sys_clk = CkeZ; + CkeZ = Cke; + end + @ (negedge Clk) begin + Sys_clk = 1'b0; + end + end + + always @ (posedge Sys_clk) begin + // Internal Commamd Pipelined + Command[0] = Command[1]; + Command[1] = Command[2]; + Command[2] = Command[3]; + Command[3] = `NOP; + + Col_addr[0] = Col_addr[1]; + Col_addr[1] = Col_addr[2]; + Col_addr[2] = Col_addr[3]; + Col_addr[3] = {col_bits{1'b0}}; + + Bank_addr[0] = Bank_addr[1]; + Bank_addr[1] = Bank_addr[2]; + Bank_addr[2] = Bank_addr[3]; + Bank_addr[3] = 2'b0; + + Bank_precharge[0] = Bank_precharge[1]; + Bank_precharge[1] = Bank_precharge[2]; + Bank_precharge[2] = Bank_precharge[3]; + Bank_precharge[3] = 2'b0; + + A10_precharge[0] = A10_precharge[1]; + A10_precharge[1] = A10_precharge[2]; + A10_precharge[2] = A10_precharge[3]; + A10_precharge[3] = 1'b0; + + // Dqm pipeline for Read + Dqm_reg0 = Dqm_reg1; + Dqm_reg1 = Dqm; + + // Read or Write with Auto Precharge Counter + if (Auto_precharge[0] === 1'b1) begin + Count_precharge[0] = Count_precharge[0] + 1; + end + if (Auto_precharge[1] === 1'b1) begin + Count_precharge[1] = Count_precharge[1] + 1; + end + if (Auto_precharge[2] === 1'b1) begin + Count_precharge[2] = Count_precharge[2] + 1; + end + if (Auto_precharge[3] === 1'b1) begin + Count_precharge[3] = Count_precharge[3] + 1; + end + + // Read or Write Interrupt Counter + if (RW_interrupt_write[0] === 1'b1) begin + RW_interrupt_counter[0] = RW_interrupt_counter[0] + 1; + end + if (RW_interrupt_write[1] === 1'b1) begin + RW_interrupt_counter[1] = RW_interrupt_counter[1] + 1; + end + if (RW_interrupt_write[2] === 1'b1) begin + RW_interrupt_counter[2] = RW_interrupt_counter[2] + 1; + end + if (RW_interrupt_write[3] === 1'b1) begin + RW_interrupt_counter[3] = RW_interrupt_counter[3] + 1; + end + + // tMRD Counter + MRD_chk = MRD_chk + 1; + + // Auto Refresh + if (Aref_enable === 1'b1) begin + if (Debug) begin + $display ("%m : at time %t AREF : Auto Refresh", $time); + end + + // Auto Refresh to Auto Refresh + if ($time - RFC_chk < tRFC) begin + $display ("%m : at time %t ERROR: tRFC violation during Auto Refresh", $time); + end + + // Precharge to Auto Refresh + if (($time - RP_chk0 < tRP) || ($time - RP_chk1 < tRP) || + ($time - RP_chk2 < tRP) || ($time - RP_chk3 < tRP)) begin + $display ("%m : at time %t ERROR: tRP violation during Auto Refresh", $time); + end + + // Precharge to Refresh + if (Pc_b0 === 1'b0 || Pc_b1 === 1'b0 || Pc_b2 === 1'b0 || Pc_b3 === 1'b0) begin + $display ("%m : at time %t ERROR: All banks must be Precharge before Auto Refresh", $time); + end + + // Load Mode Register to Auto Refresh + if (MRD_chk < tMRD) begin + $display ("%m : at time %t ERROR: tMRD violation during Auto Refresh", $time); + end + + // Record Current tRFC time + RFC_chk = $time; + end + + // Load Mode Register + if (Mode_reg_enable === 1'b1) begin + // Register Mode + Mode_reg = Addr; + + // Decode CAS Latency, Burst Length, Burst Type, and Write Burst Mode + if (Debug) begin + $display ("%m : at time %t LMR : Load Mode Register", $time); + // CAS Latency + case (Addr[6 : 4]) + 3'b010 : $display ("%m : CAS Latency = 2"); + 3'b011 : $display ("%m : CAS Latency = 3"); + default : $display ("%m : CAS Latency = Reserved"); + endcase + + // Burst Length + case (Addr[2 : 0]) + 3'b000 : $display ("%m : Burst Length = 1"); + 3'b001 : $display ("%m : Burst Length = 2"); + 3'b010 : $display ("%m : Burst Length = 4"); + 3'b011 : $display ("%m : Burst Length = 8"); + 3'b111 : $display ("%m : Burst Length = Full"); + default : $display ("%m : Burst Length = Reserved"); + endcase + + // Burst Type + if (Addr[3] === 1'b0) begin + $display ("%m : Burst Type = Sequential"); + end else if (Addr[3] === 1'b1) begin + $display ("%m : Burst Type = Interleaved"); + end else begin + $display ("%m : Burst Type = Reserved"); + end + + // Write Burst Mode + if (Addr[9] === 1'b0) begin + $display ("%m : Write Burst Mode = Programmed Burst Length"); + end else if (Addr[9] === 1'b1) begin + $display ("%m : Write Burst Mode = Single Location Access"); + end else begin + $display ("%m : Write Burst Mode = Reserved"); + end + end + + // Precharge to Load Mode Register + if (Pc_b0 === 1'b0 && Pc_b1 === 1'b0 && Pc_b2 === 1'b0 && Pc_b3 === 1'b0) begin + $display ("%m : at time %t ERROR: all banks must be Precharge before Load Mode Register", $time); + end + + // Precharge to Load Mode Register + if (($time - RP_chk0 < tRP) || ($time - RP_chk1 < tRP) || + ($time - RP_chk2 < tRP) || ($time - RP_chk3 < tRP)) begin + $display ("%m : at time %t ERROR: tRP violation during Load Mode Register", $time); + end + + // Auto Refresh to Load Mode Register + if ($time - RFC_chk < tRFC) begin + $display ("%m : at time %t ERROR: tRFC violation during Load Mode Register", $time); + end + + // Load Mode Register to Load Mode Register + if (MRD_chk < tMRD) begin + $display ("%m : at time %t ERROR: tMRD violation during Load Mode Register", $time); + end + + // Reset MRD Counter + MRD_chk = 0; + end + + // Active Block (Latch Bank Address and Row Address) + if (Active_enable === 1'b1) begin + // Activate an open bank can corrupt data + if ((Ba === 2'b00 && Act_b0 === 1'b1) || (Ba === 2'b01 && Act_b1 === 1'b1) || + (Ba === 2'b10 && Act_b2 === 1'b1) || (Ba === 2'b11 && Act_b3 === 1'b1)) begin + $display ("%m : at time %t ERROR: Bank already activated -- data can be corrupted", $time); + end + + // Activate Bank 0 + if (Ba === 2'b00 && Pc_b0 === 1'b1) begin + // Debug Message + if (Debug) begin + $display ("%m : at time %t ACT : Bank = 0 Row = %d", $time, Addr); + end + + // ACTIVE to ACTIVE command period + if ($time - RC_chk0 < tRC) begin + $display ("%m : at time %t ERROR: tRC violation during Activate bank 0", $time); + end + + // Precharge to Activate Bank 0 + if ($time - RP_chk0 < tRP) begin + $display ("%m : at time %t ERROR: tRP violation during Activate bank 0", $time); + end + + // Record variables + Act_b0 = 1'b1; + Pc_b0 = 1'b0; + B0_row_addr = Addr [addr_bits - 1 : 0]; + RAS_chk0 = $time; + RC_chk0 = $time; + RCD_chk0 = $time; + end + + if (Ba == 2'b01 && Pc_b1 == 1'b1) begin + // Debug Message + if (Debug) begin + $display ("%m : at time %t ACT : Bank = 1 Row = %d", $time, Addr); + end + + // ACTIVE to ACTIVE command period + if ($time - RC_chk1 < tRC) begin + $display ("%m : at time %t ERROR: tRC violation during Activate bank 1", $time); + end + + // Precharge to Activate Bank 1 + if ($time - RP_chk1 < tRP) begin + $display ("%m : at time %t ERROR: tRP violation during Activate bank 1", $time); + end + + // Record variables + Act_b1 = 1'b1; + Pc_b1 = 1'b0; + B1_row_addr = Addr [addr_bits - 1 : 0]; + RAS_chk1 = $time; + RC_chk1 = $time; + RCD_chk1 = $time; + end + + if (Ba == 2'b10 && Pc_b2 == 1'b1) begin + // Debug Message + if (Debug) begin + $display ("%m : at time %t ACT : Bank = 2 Row = %d", $time, Addr); + end + + // ACTIVE to ACTIVE command period + if ($time - RC_chk2 < tRC) begin + $display ("%m : at time %t ERROR: tRC violation during Activate bank 2", $time); + end + + // Precharge to Activate Bank 2 + if ($time - RP_chk2 < tRP) begin + $display ("%m : at time %t ERROR: tRP violation during Activate bank 2", $time); + end + + // Record variables + Act_b2 = 1'b1; + Pc_b2 = 1'b0; + B2_row_addr = Addr [addr_bits - 1 : 0]; + RAS_chk2 = $time; + RC_chk2 = $time; + RCD_chk2 = $time; + end + + if (Ba == 2'b11 && Pc_b3 == 1'b1) begin + // Debug Message + if (Debug) begin + $display ("%m : at time %t ACT : Bank = 3 Row = %d", $time, Addr); + end + + // ACTIVE to ACTIVE command period + if ($time - RC_chk3 < tRC) begin + $display ("%m : at time %t ERROR: tRC violation during Activate bank 3", $time); + end + + // Precharge to Activate Bank 3 + if ($time - RP_chk3 < tRP) begin + $display ("%m : at time %t ERROR: tRP violation during Activate bank 3", $time); + end + + // Record variables + Act_b3 = 1'b1; + Pc_b3 = 1'b0; + B3_row_addr = Addr [addr_bits - 1 : 0]; + RAS_chk3 = $time; + RC_chk3 = $time; + RCD_chk3 = $time; + end + + // Active Bank A to Active Bank B + if ((Prev_bank != Ba) && ($time - RRD_chk < tRRD)) begin + $display ("%m : at time %t ERROR: tRRD violation during Activate bank = %d", $time, Ba); + end + + // Auto Refresh to Activate + if ($time - RFC_chk < tRFC) begin + $display ("%m : at time %t ERROR: tRFC violation during Activate bank = %d", $time, Ba); + end + + // Load Mode Register to Active + if (MRD_chk < tMRD ) begin + $display ("%m : at time %t ERROR: tMRD violation during Activate bank = %d", $time, Ba); + end + + // Record variables for checking violation + RRD_chk = $time; + Prev_bank = Ba; + end + + // Precharge Block + if (Prech_enable == 1'b1) begin + // Load Mode Register to Precharge + if ($time - MRD_chk < tMRD) begin + $display ("%m : at time %t ERROR: tMRD violaiton during Precharge", $time); + end + + // Precharge Bank 0 + if ((Addr[10] === 1'b1 || (Addr[10] === 1'b0 && Ba === 2'b00)) && Act_b0 === 1'b1) begin + Act_b0 = 1'b0; + Pc_b0 = 1'b1; + RP_chk0 = $time; + + // Activate to Precharge + if ($time - RAS_chk0 < tRAS) begin + $display ("%m : at time %t ERROR: tRAS violation during Precharge", $time); + end + + // tWR violation check for write + if ($time - WR_chkm[0] < tWRm) begin + $display ("%m : at time %t ERROR: tWR violation during Precharge", $time); + end + end + + // Precharge Bank 1 + if ((Addr[10] === 1'b1 || (Addr[10] === 1'b0 && Ba === 2'b01)) && Act_b1 === 1'b1) begin + Act_b1 = 1'b0; + Pc_b1 = 1'b1; + RP_chk1 = $time; + + // Activate to Precharge + if ($time - RAS_chk1 < tRAS) begin + $display ("%m : at time %t ERROR: tRAS violation during Precharge", $time); + end + + // tWR violation check for write + if ($time - WR_chkm[1] < tWRm) begin + $display ("%m : at time %t ERROR: tWR violation during Precharge", $time); + end + end + + // Precharge Bank 2 + if ((Addr[10] === 1'b1 || (Addr[10] === 1'b0 && Ba === 2'b10)) && Act_b2 === 1'b1) begin + Act_b2 = 1'b0; + Pc_b2 = 1'b1; + RP_chk2 = $time; + + // Activate to Precharge + if ($time - RAS_chk2 < tRAS) begin + $display ("%m : at time %t ERROR: tRAS violation during Precharge", $time); + end + + // tWR violation check for write + if ($time - WR_chkm[2] < tWRm) begin + $display ("%m : at time %t ERROR: tWR violation during Precharge", $time); + end + end + + // Precharge Bank 3 + if ((Addr[10] === 1'b1 || (Addr[10] === 1'b0 && Ba === 2'b11)) && Act_b3 === 1'b1) begin + Act_b3 = 1'b0; + Pc_b3 = 1'b1; + RP_chk3 = $time; + + // Activate to Precharge + if ($time - RAS_chk3 < tRAS) begin + $display ("%m : at time %t ERROR: tRAS violation during Precharge", $time); + end + + // tWR violation check for write + if ($time - WR_chkm[3] < tWRm) begin + $display ("%m : at time %t ERROR: tWR violation during Precharge", $time); + end + end + + // Terminate a Write Immediately (if same bank or all banks) + if (Data_in_enable === 1'b1 && (Bank === Ba || Addr[10] === 1'b1)) begin + Data_in_enable = 1'b0; + end + + // Precharge Command Pipeline for Read + if (Cas_latency_3 === 1'b1) begin + Command[2] = `PRECH; + Bank_precharge[2] = Ba; + A10_precharge[2] = Addr[10]; + end else if (Cas_latency_2 === 1'b1) begin + Command[1] = `PRECH; + Bank_precharge[1] = Ba; + A10_precharge[1] = Addr[10]; + end + end + + // Burst terminate + if (Burst_term === 1'b1) begin + // Terminate a Write Immediately + if (Data_in_enable == 1'b1) begin + Data_in_enable = 1'b0; + end + + // Terminate a Read Depend on CAS Latency + if (Cas_latency_3 === 1'b1) begin + Command[2] = `BST; + end else if (Cas_latency_2 == 1'b1) begin + Command[1] = `BST; + end + + // Display debug message + if (Debug) begin + $display ("%m : at time %t BST : Burst Terminate",$time); + end + end + + // Read, Write, Column Latch + if (Read_enable === 1'b1) begin + // Check to see if bank is open (ACT) + if ((Ba == 2'b00 && Pc_b0 == 1'b1) || (Ba == 2'b01 && Pc_b1 == 1'b1) || + (Ba == 2'b10 && Pc_b2 == 1'b1) || (Ba == 2'b11 && Pc_b3 == 1'b1)) begin + $display("%m : at time %t ERROR: Bank is not Activated for Read", $time); + end + + // Activate to Read or Write + if ((Ba == 2'b00) && ($time - RCD_chk0 < tRCD) || + (Ba == 2'b01) && ($time - RCD_chk1 < tRCD) || + (Ba == 2'b10) && ($time - RCD_chk2 < tRCD) || + (Ba == 2'b11) && ($time - RCD_chk3 < tRCD)) begin + $display("%m : at time %t ERROR: tRCD violation during Read", $time); + end + + // CAS Latency pipeline + if (Cas_latency_3 == 1'b1) begin + Command[2] = `READ; + Col_addr[2] = Addr; + Bank_addr[2] = Ba; + end else if (Cas_latency_2 == 1'b1) begin + Command[1] = `READ; + Col_addr[1] = Addr; + Bank_addr[1] = Ba; + end + + // Read interrupt Write (terminate Write immediately) + if (Data_in_enable == 1'b1) begin + Data_in_enable = 1'b0; + + // Interrupting a Write with Autoprecharge + if (Auto_precharge[RW_interrupt_bank] == 1'b1 && Write_precharge[RW_interrupt_bank] == 1'b1) begin + RW_interrupt_write[RW_interrupt_bank] = 1'b1; + RW_interrupt_counter[RW_interrupt_bank] = 0; + + // Display debug message + if (Debug) begin + $display ("%m : at time %t NOTE : Read interrupt Write with Autoprecharge", $time); + end + end + end + + // Write with Auto Precharge + if (Addr[10] == 1'b1) begin + Auto_precharge[Ba] = 1'b1; + Count_precharge[Ba] = 0; + RW_interrupt_bank = Ba; + Read_precharge[Ba] = 1'b1; + end + end + + // Write Command + if (Write_enable == 1'b1) begin + // Activate to Write + if ((Ba == 2'b00 && Pc_b0 == 1'b1) || (Ba == 2'b01 && Pc_b1 == 1'b1) || + (Ba == 2'b10 && Pc_b2 == 1'b1) || (Ba == 2'b11 && Pc_b3 == 1'b1)) begin + $display("%m : at time %t ERROR: Bank is not Activated for Write", $time); + end + + // Activate to Read or Write + if ((Ba == 2'b00) && ($time - RCD_chk0 < tRCD) || + (Ba == 2'b01) && ($time - RCD_chk1 < tRCD) || + (Ba == 2'b10) && ($time - RCD_chk2 < tRCD) || + (Ba == 2'b11) && ($time - RCD_chk3 < tRCD)) begin + $display("%m : at time %t ERROR: tRCD violation during Read", $time); + end + + // Latch Write command, Bank, and Column + Command[0] = `WRITE; + Col_addr[0] = Addr; + Bank_addr[0] = Ba; + + // Write interrupt Write (terminate Write immediately) + if (Data_in_enable == 1'b1) begin + Data_in_enable = 1'b0; + + // Interrupting a Write with Autoprecharge + if (Auto_precharge[RW_interrupt_bank] == 1'b1 && Write_precharge[RW_interrupt_bank] == 1'b1) begin + RW_interrupt_write[RW_interrupt_bank] = 1'b1; + + // Display debug message + if (Debug) begin + $display ("%m : at time %t NOTE : Read Bank %d interrupt Write Bank %d with Autoprecharge", $time, Ba, RW_interrupt_bank); + end + end + end + + // Write interrupt Read (terminate Read immediately) + if (Data_out_enable == 1'b1) begin + Data_out_enable = 1'b0; + + // Interrupting a Read with Autoprecharge + if (Auto_precharge[RW_interrupt_bank] == 1'b1 && Read_precharge[RW_interrupt_bank] == 1'b1) begin + RW_interrupt_read[RW_interrupt_bank] = 1'b1; + + // Display debug message + if (Debug) begin + $display ("%m : at time %t NOTE : Write Bank %d interrupt Read Bank %d with Autoprecharge", $time, Ba, RW_interrupt_bank); + end + end + end + + // Write with Auto Precharge + if (Addr[10] == 1'b1) begin + Auto_precharge[Ba] = 1'b1; + Count_precharge[Ba] = 0; + RW_interrupt_bank = Ba; + Write_precharge[Ba] = 1'b1; + end + end + + /* + Write with Auto Precharge Calculation + The device start internal precharge when: + 1. Meet minimum tRAS requirement + and 2. tWR cycle(s) after last valid data + or 3. Interrupt by a Read or Write (with or without Auto Precharge) + + Note: Model is starting the internal precharge 1 cycle after they meet all the + requirement but tRP will be compensate for the time after the 1 cycle. + */ + if ((Auto_precharge[0] == 1'b1) && (Write_precharge[0] == 1'b1)) begin + if ((($time - RAS_chk0 >= tRAS) && // Case 1 + (((Burst_length_1 == 1'b1 || Write_burst_mode == 1'b1) && Count_precharge [0] >= 1) || // Case 2 + (Burst_length_2 == 1'b1 && Count_precharge [0] >= 2) || + (Burst_length_4 == 1'b1 && Count_precharge [0] >= 4) || + (Burst_length_8 == 1'b1 && Count_precharge [0] >= 8))) || + (RW_interrupt_write[0] == 1'b1 && RW_interrupt_counter[0] >= 1)) begin // Case 3 + Auto_precharge[0] = 1'b0; + Write_precharge[0] = 1'b0; + RW_interrupt_write[0] = 1'b0; + Pc_b0 = 1'b1; + Act_b0 = 1'b0; + RP_chk0 = $time + tWRa; + if (Debug) begin + $display ("%m : at time %t NOTE : Start Internal Auto Precharge for Bank 0", $time); + end + end + end + if ((Auto_precharge[1] == 1'b1) && (Write_precharge[1] == 1'b1)) begin + if ((($time - RAS_chk1 >= tRAS) && // Case 1 + (((Burst_length_1 == 1'b1 || Write_burst_mode == 1'b1) && Count_precharge [1] >= 1) || // Case 2 + (Burst_length_2 == 1'b1 && Count_precharge [1] >= 2) || + (Burst_length_4 == 1'b1 && Count_precharge [1] >= 4) || + (Burst_length_8 == 1'b1 && Count_precharge [1] >= 8))) || + (RW_interrupt_write[1] == 1'b1 && RW_interrupt_counter[1] >= 1)) begin // Case 3 + Auto_precharge[1] = 1'b0; + Write_precharge[1] = 1'b0; + RW_interrupt_write[1] = 1'b0; + Pc_b1 = 1'b1; + Act_b1 = 1'b0; + RP_chk1 = $time + tWRa; + if (Debug) begin + $display ("%m : at time %t NOTE : Start Internal Auto Precharge for Bank 1", $time); + end + end + end + if ((Auto_precharge[2] == 1'b1) && (Write_precharge[2] == 1'b1)) begin + if ((($time - RAS_chk2 >= tRAS) && // Case 1 + (((Burst_length_1 == 1'b1 || Write_burst_mode == 1'b1) && Count_precharge [2] >= 1) || // Case 2 + (Burst_length_2 == 1'b1 && Count_precharge [2] >= 2) || + (Burst_length_4 == 1'b1 && Count_precharge [2] >= 4) || + (Burst_length_8 == 1'b1 && Count_precharge [2] >= 8))) || + (RW_interrupt_write[2] == 1'b1 && RW_interrupt_counter[2] >= 1)) begin // Case 3 + Auto_precharge[2] = 1'b0; + Write_precharge[2] = 1'b0; + RW_interrupt_write[2] = 1'b0; + Pc_b2 = 1'b1; + Act_b2 = 1'b0; + RP_chk2 = $time + tWRa; + if (Debug) begin + $display ("%m : at time %t NOTE : Start Internal Auto Precharge for Bank 2", $time); + end + end + end + if ((Auto_precharge[3] == 1'b1) && (Write_precharge[3] == 1'b1)) begin + if ((($time - RAS_chk3 >= tRAS) && // Case 1 + (((Burst_length_1 == 1'b1 || Write_burst_mode == 1'b1) && Count_precharge [3] >= 1) || // Case 2 + (Burst_length_2 == 1'b1 && Count_precharge [3] >= 2) || + (Burst_length_4 == 1'b1 && Count_precharge [3] >= 4) || + (Burst_length_8 == 1'b1 && Count_precharge [3] >= 8))) || + (RW_interrupt_write[3] == 1'b1 && RW_interrupt_counter[3] >= 1)) begin // Case 3 + Auto_precharge[3] = 1'b0; + Write_precharge[3] = 1'b0; + RW_interrupt_write[3] = 1'b0; + Pc_b3 = 1'b1; + Act_b3 = 1'b0; + RP_chk3 = $time + tWRa; + if (Debug) begin + $display ("%m : at time %t NOTE : Start Internal Auto Precharge for Bank 3", $time); + end + end + end + + // Read with Auto Precharge Calculation + // The device start internal precharge: + // 1. Meet minimum tRAS requirement + // and 2. CAS Latency - 1 cycles before last burst + // or 3. Interrupt by a Read or Write (with or without AutoPrecharge) + if ((Auto_precharge[0] == 1'b1) && (Read_precharge[0] == 1'b1)) begin + if ((($time - RAS_chk0 >= tRAS) && // Case 1 + ((Burst_length_1 == 1'b1 && Count_precharge[0] >= 1) || // Case 2 + (Burst_length_2 == 1'b1 && Count_precharge[0] >= 2) || + (Burst_length_4 == 1'b1 && Count_precharge[0] >= 4) || + (Burst_length_8 == 1'b1 && Count_precharge[0] >= 8))) || + (RW_interrupt_read[0] == 1'b1)) begin // Case 3 + Pc_b0 = 1'b1; + Act_b0 = 1'b0; + RP_chk0 = $time; + Auto_precharge[0] = 1'b0; + Read_precharge[0] = 1'b0; + RW_interrupt_read[0] = 1'b0; + if (Debug) begin + $display ("%m : at time %t NOTE : Start Internal Auto Precharge for Bank 0", $time); + end + end + end + if ((Auto_precharge[1] == 1'b1) && (Read_precharge[1] == 1'b1)) begin + if ((($time - RAS_chk1 >= tRAS) && + ((Burst_length_1 == 1'b1 && Count_precharge[1] >= 1) || + (Burst_length_2 == 1'b1 && Count_precharge[1] >= 2) || + (Burst_length_4 == 1'b1 && Count_precharge[1] >= 4) || + (Burst_length_8 == 1'b1 && Count_precharge[1] >= 8))) || + (RW_interrupt_read[1] == 1'b1)) begin + Pc_b1 = 1'b1; + Act_b1 = 1'b0; + RP_chk1 = $time; + Auto_precharge[1] = 1'b0; + Read_precharge[1] = 1'b0; + RW_interrupt_read[1] = 1'b0; + if (Debug) begin + $display ("%m : at time %t NOTE : Start Internal Auto Precharge for Bank 1", $time); + end + end + end + if ((Auto_precharge[2] == 1'b1) && (Read_precharge[2] == 1'b1)) begin + if ((($time - RAS_chk2 >= tRAS) && + ((Burst_length_1 == 1'b1 && Count_precharge[2] >= 1) || + (Burst_length_2 == 1'b1 && Count_precharge[2] >= 2) || + (Burst_length_4 == 1'b1 && Count_precharge[2] >= 4) || + (Burst_length_8 == 1'b1 && Count_precharge[2] >= 8))) || + (RW_interrupt_read[2] == 1'b1)) begin + Pc_b2 = 1'b1; + Act_b2 = 1'b0; + RP_chk2 = $time; + Auto_precharge[2] = 1'b0; + Read_precharge[2] = 1'b0; + RW_interrupt_read[2] = 1'b0; + if (Debug) begin + $display ("%m : at time %t NOTE : Start Internal Auto Precharge for Bank 2", $time); + end + end + end + if ((Auto_precharge[3] == 1'b1) && (Read_precharge[3] == 1'b1)) begin + if ((($time - RAS_chk3 >= tRAS) && + ((Burst_length_1 == 1'b1 && Count_precharge[3] >= 1) || + (Burst_length_2 == 1'b1 && Count_precharge[3] >= 2) || + (Burst_length_4 == 1'b1 && Count_precharge[3] >= 4) || + (Burst_length_8 == 1'b1 && Count_precharge[3] >= 8))) || + (RW_interrupt_read[3] == 1'b1)) begin + Pc_b3 = 1'b1; + Act_b3 = 1'b0; + RP_chk3 = $time; + Auto_precharge[3] = 1'b0; + Read_precharge[3] = 1'b0; + RW_interrupt_read[3] = 1'b0; + if (Debug) begin + $display("%m : at time %t NOTE : Start Internal Auto Precharge for Bank 3", $time); + end + end + end + + // Internal Precharge or Bst + if (Command[0] == `PRECH) begin // Precharge terminate a read with same bank or all banks + if (Bank_precharge[0] == Bank || A10_precharge[0] == 1'b1) begin + if (Data_out_enable == 1'b1) begin + Data_out_enable = 1'b0; + end + end + end else if (Command[0] == `BST) begin // BST terminate a read to current bank + if (Data_out_enable == 1'b1) begin + Data_out_enable = 1'b0; + end + end + + if (Data_out_enable == 1'b0) begin + Dq_reg <= #tOH {data_bits{1'bz}}; + end + + // Detect Read or Write command + if (Command[0] == `READ) begin + Bank = Bank_addr[0]; + Col = Col_addr[0]; + Col_brst = Col_addr[0]; + case (Bank_addr[0]) + 2'b00 : Row = B0_row_addr; + 2'b01 : Row = B1_row_addr; + 2'b10 : Row = B2_row_addr; + 2'b11 : Row = B3_row_addr; + endcase + Burst_counter = 0; + Data_in_enable = 1'b0; + Data_out_enable = 1'b1; + end else if (Command[0] == `WRITE) begin + Bank = Bank_addr[0]; + Col = Col_addr[0]; + Col_brst = Col_addr[0]; + case (Bank_addr[0]) + 2'b00 : Row = B0_row_addr; + 2'b01 : Row = B1_row_addr; + 2'b10 : Row = B2_row_addr; + 2'b11 : Row = B3_row_addr; + endcase + Burst_counter = 0; + Data_in_enable = 1'b1; + Data_out_enable = 1'b0; + end + + // DQ buffer (Driver/Receiver) + if (Data_in_enable == 1'b1) begin // Writing Data to Memory + // Array buffer + case (Bank) + 2'b00 : Dq_dqm = Bank0 [{Row, Col}]; + 2'b01 : Dq_dqm = Bank1 [{Row, Col}]; + 2'b10 : Dq_dqm = Bank2 [{Row, Col}]; + 2'b11 : Dq_dqm = Bank3 [{Row, Col}]; + endcase + + // Dqm operation + if (Dqm[0] == 1'b0) begin + Dq_dqm [ 7 : 0] = Dq [ 7 : 0]; + end + if (Dqm[1] == 1'b0) begin + Dq_dqm [15 : 8] = Dq [15 : 8]; + end + + // Write to memory + case (Bank) + 2'b00 : Bank0 [{Row, Col}] = Dq_dqm; + 2'b01 : Bank1 [{Row, Col}] = Dq_dqm; + 2'b10 : Bank2 [{Row, Col}] = Dq_dqm; + 2'b11 : Bank3 [{Row, Col}] = Dq_dqm; + endcase + + // Display debug message + if (Dqm !== 2'b11) begin + // Record tWR for manual precharge + WR_chkm [Bank] = $time; + + if (Debug) begin + $display("%m : at time %t WRITE: Bank = %d Row = %d, Col = %d, Data = %d", $time, Bank, Row, Col, Dq_dqm); + end + end else begin + if (Debug) begin + $display("%m : at time %t WRITE: Bank = %d Row = %d, Col = %d, Data = Hi-Z due to DQM", $time, Bank, Row, Col); + end + end + + // Advance burst counter subroutine + #tHZ Burst_decode; + + end else if (Data_out_enable == 1'b1) begin // Reading Data from Memory + // Array buffer + case (Bank) + 2'b00 : Dq_dqm = Bank0[{Row, Col}]; + 2'b01 : Dq_dqm = Bank1[{Row, Col}]; + 2'b10 : Dq_dqm = Bank2[{Row, Col}]; + 2'b11 : Dq_dqm = Bank3[{Row, Col}]; + endcase + + // Dqm operation + if (Dqm_reg0 [0] == 1'b1) begin + Dq_dqm [ 7 : 0] = 8'bz; + end + if (Dqm_reg0 [1] == 1'b1) begin + Dq_dqm [15 : 8] = 8'bz; + end + + // Display debug message + if (Dqm_reg0 !== 2'b11) begin + Dq_reg = #tAC Dq_dqm; + if (Debug) begin + $display("%m : at time %t READ : Bank = %d Row = %d, Col = %d, Data = %d", $time, Bank, Row, Col, Dq_reg); + end + end else begin + Dq_reg = #tHZ {data_bits{1'bz}}; + if (Debug) begin + $display("%m : at time %t READ : Bank = %d Row = %d, Col = %d, Data = Hi-Z due to DQM", $time, Bank, Row, Col); + end + end + + // Advance burst counter subroutine + Burst_decode; + end + end + + // Burst counter decode + task Burst_decode; + begin + // Advance Burst Counter + Burst_counter = Burst_counter + 1; + + // Burst Type + if (Mode_reg[3] == 1'b0) begin // Sequential Burst + Col_temp = Col + 1; + end else if (Mode_reg[3] == 1'b1) begin // Interleaved Burst + Col_temp[2] = Burst_counter[2] ^ Col_brst[2]; + Col_temp[1] = Burst_counter[1] ^ Col_brst[1]; + Col_temp[0] = Burst_counter[0] ^ Col_brst[0]; + end + + // Burst Length + if (Burst_length_2) begin // Burst Length = 2 + Col [0] = Col_temp [0]; + end else if (Burst_length_4) begin // Burst Length = 4 + Col [1 : 0] = Col_temp [1 : 0]; + end else if (Burst_length_8) begin // Burst Length = 8 + Col [2 : 0] = Col_temp [2 : 0]; + end else begin // Burst Length = FULL + Col = Col_temp; + end + + // Burst Read Single Write + if (Write_burst_mode == 1'b1) begin + Data_in_enable = 1'b0; + end + + // Data Counter + if (Burst_length_1 == 1'b1) begin + if (Burst_counter >= 1) begin + Data_in_enable = 1'b0; + Data_out_enable = 1'b0; + end + end else if (Burst_length_2 == 1'b1) begin + if (Burst_counter >= 2) begin + Data_in_enable = 1'b0; + Data_out_enable = 1'b0; + end + end else if (Burst_length_4 == 1'b1) begin + if (Burst_counter >= 4) begin + Data_in_enable = 1'b0; + Data_out_enable = 1'b0; + end + end else if (Burst_length_8 == 1'b1) begin + if (Burst_counter >= 8) begin + Data_in_enable = 1'b0; + Data_out_enable = 1'b0; + end + end + end + endtask + + // Timing Parameters for -7E (133 MHz @ CL2) + specify + specparam + tAH = 0.8, // Addr, Ba Hold Time + tAS = 1.5, // Addr, Ba Setup Time + tCH = 2.5, // Clock High-Level Width + tCL = 2.5, // Clock Low-Level Width + tCK = 7.0, // Clock Cycle Time + tDH = 0.8, // Data-in Hold Time + tDS = 1.5, // Data-in Setup Time + tCKH = 0.8, // CKE Hold Time + tCKS = 1.5, // CKE Setup Time + tCMH = 0.8, // CS#, RAS#, CAS#, WE#, DQM# Hold Time + tCMS = 1.5; // CS#, RAS#, CAS#, WE#, DQM# Setup Time + $width (posedge Clk, tCH); + $width (negedge Clk, tCL); + $period (negedge Clk, tCK); + $period (posedge Clk, tCK); + $setuphold(posedge Clk, Cke, tCKS, tCKH); + $setuphold(posedge Clk, Cs_n, tCMS, tCMH); + $setuphold(posedge Clk, Cas_n, tCMS, tCMH); + $setuphold(posedge Clk, Ras_n, tCMS, tCMH); + $setuphold(posedge Clk, We_n, tCMS, tCMH); + $setuphold(posedge Clk, Addr, tAS, tAH); + $setuphold(posedge Clk, Ba, tAS, tAH); + $setuphold(posedge Clk, Dqm, tCMS, tCMH); + $setuphold(posedge Dq_chk, Dq, tDS, tDH); + endspecify + +endmodule
diff --git a/spinal-cocotb/SpinalNet/test/src/verilog/tb_sdram_controller.v b/spinal-cocotb/SpinalNet/test/src/verilog/tb_sdram_controller.v new file mode 100644 index 0000000..335cc91 --- /dev/null +++ b/spinal-cocotb/SpinalNet/test/src/verilog/tb_sdram_controller.v
@@ -0,0 +1,123 @@ + +module tb_sdram_controller ( + input io_axi_aw_valid, + output io_axi_aw_ready, + input [31:0] io_axi_aw_payload_addr, + input [3:0] io_axi_aw_payload_id, + input [7:0] io_axi_aw_payload_len, + input [2:0] io_axi_aw_payload_size, + input [1:0] io_axi_aw_payload_burst, + input io_axi_w_valid, + output io_axi_w_ready, + input [31:0] io_axi_w_payload_data, + input [3:0] io_axi_w_payload_strb, + input io_axi_w_payload_last, + output io_axi_b_valid, + input io_axi_b_ready, + output [3:0] io_axi_b_payload_id, + output [1:0] io_axi_b_payload_resp, + input io_axi_ar_valid, + output io_axi_ar_ready, + input [31:0] io_axi_ar_payload_addr, + input [3:0] io_axi_ar_payload_id, + input [7:0] io_axi_ar_payload_len, + input [2:0] io_axi_ar_payload_size, + input [1:0] io_axi_ar_payload_burst, + output io_axi_r_valid, + input io_axi_r_ready, + output [31:0] io_axi_r_payload_data, + output [3:0] io_axi_r_payload_id, + output [1:0] io_axi_r_payload_resp, + output io_axi_r_payload_last, + output io_initDone, + input clk, + input reset +); + wire [12:0] controller_io_sdram_ADDR; + wire [1:0] controller_io_sdram_BA; + wire controller_io_sdram_CASn; + wire controller_io_sdram_CKE; + wire controller_io_sdram_CSn; + wire [1:0] controller_io_sdram_DQM; + wire controller_io_sdram_RASn; + wire controller_io_sdram_WEn; + wire [15:0] controller_io_sdram_DQ_write; + wire [15:0] controller_io_sdram_DQ_writeEnable; + wire [15:0] sdramDevice_DQ_read; + + SdramController controller ( + .io_axi_aw_valid (io_axi_aw_valid ), //i + .io_axi_aw_ready (io_axi_aw_ready ), //o + .io_axi_aw_payload_addr (io_axi_aw_payload_addr ), //i + .io_axi_aw_payload_id (io_axi_aw_payload_id ), //i + .io_axi_aw_payload_len (io_axi_aw_payload_len ), //i + .io_axi_aw_payload_size (io_axi_aw_payload_size ), //i + .io_axi_aw_payload_burst (io_axi_aw_payload_burst ), //i + .io_axi_w_valid (io_axi_w_valid ), //i + .io_axi_w_ready (io_axi_w_ready ), //o + .io_axi_w_payload_data (io_axi_w_payload_data ), //i + .io_axi_w_payload_strb (io_axi_w_payload_strb ), //i + .io_axi_w_payload_last (io_axi_w_payload_last ), //i + .io_axi_b_valid (io_axi_b_valid ), //o + .io_axi_b_ready (io_axi_b_ready ), //i + .io_axi_b_payload_id (io_axi_b_payload_id ), //o + .io_axi_b_payload_resp (io_axi_b_payload_resp ), //o + .io_axi_ar_valid (io_axi_ar_valid ), //i + .io_axi_ar_ready (io_axi_ar_ready ), //o + .io_axi_ar_payload_addr (io_axi_ar_payload_addr ), //i + .io_axi_ar_payload_id (io_axi_ar_payload_id ), //i + .io_axi_ar_payload_len (io_axi_ar_payload_len ), //i + .io_axi_ar_payload_size (io_axi_ar_payload_size ), //i + .io_axi_ar_payload_burst (io_axi_ar_payload_burst ), //i + .io_axi_r_valid (io_axi_r_valid ), //o + .io_axi_r_ready (io_axi_r_ready ), //i + .io_axi_r_payload_data (io_axi_r_payload_data ), //o + .io_axi_r_payload_id (io_axi_r_payload_id ), //o + .io_axi_r_payload_resp (io_axi_r_payload_resp ), //o + .io_axi_r_payload_last (io_axi_r_payload_last ), //o + .io_sdram_ADDR (controller_io_sdram_ADDR[12:0] ), //o + .io_sdram_BA (controller_io_sdram_BA[1:0] ), //o + .io_sdram_DQ_read (sdramDevice_DQ_read[15:0] ), //i + .io_sdram_DQ_write (controller_io_sdram_DQ_write[15:0] ), //o + .io_sdram_DQ_writeEnable (controller_io_sdram_DQ_writeEnable[15:0] ), //o + .io_sdram_DQM (controller_io_sdram_DQM[1:0] ), //o + .io_sdram_CASn (controller_io_sdram_CASn ), //o + .io_sdram_CKE (controller_io_sdram_CKE ), //o + .io_sdram_CSn (controller_io_sdram_CSn ), //o + .io_sdram_RASn (controller_io_sdram_RASn ), //o + .io_sdram_WEn (controller_io_sdram_WEn ), //o + .io_initDone (io_initDone ), //o + .clk (clk ), //i + .reset (reset ) //i + ); + + wire [15:0] io_sdram_DQ; + assign sdramDevice_DQ_read = io_sdram_DQ; + assign io_sdram_DQ = controller_io_sdram_DQ_writeEnable ? controller_io_sdram_DQ_write : 16'bZZZZZZZZZZZZZZZZ; + + // sdram_model_plus #( + // .addr_bits (12 ), // 地址位宽 + // .data_bits (16 ), // 数据位宽 + // .col_bits (9 ), // col地址位宽A0-A8 + // .mem_sizes (2*1024*1024-1) // 2M + // ) sdramDeivce ( + // .Debug (1'b1 ), //i + mt48lc16m16a2 sdramDevice ( + .Clk (~clk ), //i + .Addr (controller_io_sdram_ADDR[12:0] ), //i + .Ba (controller_io_sdram_BA[1:0] ), //i + .Dq (io_sdram_DQ ), //io + .Dqm (controller_io_sdram_DQM[1:0] ), //i + .Cas_n (controller_io_sdram_CASn ), //i + .Cke (controller_io_sdram_CKE ), //i + .Cs_n (controller_io_sdram_CSn ), //i + .Ras_n (controller_io_sdram_RASn ), //i + .We_n (controller_io_sdram_WEn ) //i + ); + + initial begin + $dumpfile ("wave.vcd"); + $dumpvars; + #1; + end +endmodule
diff --git a/spinal-cocotb/run.sh b/spinal-cocotb/run.sh new file mode 100755 index 0000000..f26e1c0 --- /dev/null +++ b/spinal-cocotb/run.sh
@@ -0,0 +1,27 @@ +#! /bin/sh + +set -o errexit +set -o nounset +set -o xtrace + +MILL_VERSION=0.9.7 + +if [ ! -f mill ]; then + curl -L https://github.com/com-lihaoyi/mill/releases/download/$MILL_VERSION/$MILL_VERSION > mill && chmod +x mill +fi + +./mill version + +# Check format +./mill SpinalNet.checkFormat +./mill SpinalNet.fix --check + + +# Run test and simulation +./mill SpinalNet.test.testOnly dma.DmaControllerTest +./mill SpinalNet.test.testOnly sdram.SdramControllerTest +./mill SpinalNet.test.testOnly udp.UdpTest + +# Generate Verilog code for Caravel +./mill SpinalNet.runMain dma.DmaMem +
diff --git a/spinal-cocotb/setup.sh b/spinal-cocotb/setup.sh new file mode 100755 index 0000000..138245d --- /dev/null +++ b/spinal-cocotb/setup.sh
@@ -0,0 +1,15 @@ +#! /bin/sh + +set -o errexit +set -o nounset +set -o xtrace + +sudo apt-get update +sudo apt-get install -y default-jdk iverilog verilator + +# Add Spinal HDL CocotbLib +git submodule add https://github.com/SpinalHDL/CocotbLib.git SpinalNet/test/src/python/cocotblib +# Local install Cocotb and set PATH env +pip3 install cocotb +export PATH="$HOME/.local/bin:$PATH" +