Commit 3f3fef24 authored by Jens Korinth's avatar Jens Korinth
Browse files

DataWidthConverter: Fix bugs in implementation

* previous implementation was not capable of handling a continuous
  stream of data (continuity must be preserved to avoid congestion)
* new implementation fixes this and several other minor issues
* better unit test: now using inverted pairs of DWCs
* makes testing code much simpler and data/waves easier to check
* added additional tests to specify the required timing behavior
  (i.e., preserving continuous data streams)
parent f0013921
......@@ -34,81 +34,68 @@ class DataWidthConverter(
}
val ratio: Int = if (inWidth > outWidth) inWidth / outWidth else outWidth / inWidth
val d_w = if (inWidth > outWidth) inWidth else outWidth // data register
val d = Reg(UInt(width = d_w)) // current value
val d_hs = Reg(Bool()) // handshake input
val i = Reg(UInt(width = log2Up(ratio))) // current byte index
val d_w = if (inWidth > outWidth) inWidth else outWidth // data register width
val inq_ready = Reg(Bool()) // input data ready
val inq_valid = RegNext(io.inq.valid) // input data valid?
val deq_ready = RegNext(io.deq.ready) // output data ready?
val deq_valid = Reg(Bool()) // output data valid
if (inWidth > outWidth)
downsize()
else
upsize()
inq_ready := !reset && !d_hs
deq_valid := !reset && d_hs
io.inq.ready := inq_ready
io.deq.valid := deq_valid
private def upsize() = {
val i = Reg(UInt(width = log2Up(ratio + 1)))
val d = Reg(UInt(width = outWidth))
val c = Reg(init = Bool(true))
when (reset) {
d := UInt(0)
d_hs := Bool(false)
i := UInt(if (littleEndian) 0 else ratio - 1)
inq_ready := Bool(false)
deq_valid := Bool(false)
io.inq.ready := !reset && (i =/= UInt(0) || io.deq.ready)
io.deq.bits := d
io.deq.valid := !reset && (i === UInt(0) && c)
when (reset) {
i := UInt(ratio)
d := UInt(0)
}
.otherwise {
when (io.inq.ready && io.inq.valid) {
c := Bool(true)
if (littleEndian)
d := Cat(io.inq.bits, d) >> UInt(inWidth)
else
d := (d << UInt(inWidth)) | io.inq.bits
i := Mux(i === UInt(0), UInt(ratio - 1), i - UInt(1))
}
when (io.deq.valid && io.deq.ready) { c := Bool(false) }
}
}
if (inWidth > outWidth) {
val outFifo = Module(new Queue(UInt(width = outWidth), ratio))
outFifo.io.deq <> io.deq
outFifo.io.enq.bits := d(i * UInt(outWidth) + UInt(outWidth), i * UInt(outWidth))
outFifo.io.enq.valid := d_hs
val out_ready = RegNext(outFifo.io.enq.ready)
private def downsize() = {
val i = Reg(UInt(width = log2Up(ratio)))
val d = Reg(UInt(width = inWidth))
io.inq.ready := !reset && i === UInt(0)
if (littleEndian)
io.deq.bits := d(outWidth - 1, 0)
else
io.deq.bits := d(inWidth - 1, inWidth - outWidth)
io.deq.valid := !reset && RegNext(i > UInt(0) || io.inq.valid)
when (reset) {}
when (reset) {
i := UInt(0)
d := UInt(0)
}
.otherwise {
when (inq_ready && inq_valid) {
d := io.inq.bits
d_hs := Bool(true)
inq_ready := Bool(false)
when (i > UInt(0) && io.deq.ready) {
if (littleEndian)
d := d >> UInt(outWidth)
else
d := d << UInt(outWidth)
i := i - UInt(1)
}
when (d_hs) {
when (out_ready) {
if (littleEndian) {
i := Mux(i === UInt(ratio - 1), UInt(0), i + UInt(1))
when (i === UInt(ratio - 1)) { d_hs := Bool(false) }
} else {
i := Mux(i === UInt(0), UInt(ratio - 1), i - UInt(1))
when (i === UInt(0)) { d_hs := Bool(false) }
}
}
}
}
} else {
io.deq.bits := d
io.deq.valid := d_hs
val out_ready = RegNext(io.deq.ready)
when (!d_hs && inq_ready && inq_valid) {
if (littleEndian) {
d := (d << UInt(inWidth)) | io.inq.bits
i := Mux(i === UInt(ratio - 1), UInt(0), i + UInt(1))
when (i === UInt(ratio - 1)) {
d_hs := Bool(true)
inq_ready := Bool(false)
}
} else {
d := Cat(io.inq.bits, d) >> UInt(inWidth)
i := Mux(i === UInt(0), UInt(ratio - 1), i - UInt(1))
when (i === UInt(0)) {
d_hs := Bool(true)
inq_ready := Bool(false)
}
when (i === UInt(0) && io.inq.valid) {
d := io.inq.bits
i := UInt(ratio - 1)
}
}
when (d_hs && out_ready) {
d_hs := Bool(false)
d := UInt(0)
}
}
}
......@@ -7,111 +7,155 @@ import scala.math._
import java.nio.file.Paths
/**
* DataWidthConverterHarness: Attaches data source to DWC.
* DataWidthConverterHarness: Correctness test harness.
* A DecoupledDataSource with random data is connected to a pair
* of data width converters with inverted params. This circuit
* must behave exactly like a delay on the input stream (where
* the length of the delay is 2 * in/out-width-ratio).
**/
class DataWidthConverterHarness(inWidth: Int, outWidth: Int, littleEndian: Boolean) extends Module {
val io = new Bundle
val dwc = Module(new DataWidthConverter(inWidth, outWidth, littleEndian))
val dsrc = Module(new DecoupledDataSource(UInt(width = inWidth),
Seq(Seq(pow(2, inWidth).toInt, dwc.ratio).max, 10000).min,
n => UInt(n % pow(2, inWidth).toInt, width = inWidth),
//n => UInt(n % pow(2, inWidth).toInt + 1, width = inWidth),
n => UInt((scala.math.random * pow(2, inWidth)).toInt, width = inWidth),
repeat = false))
val dwc2 = Module(new DataWidthConverter(outWidth, inWidth, littleEndian))
dwc.io.inq <> dsrc.io.out
dwc.io.deq.ready := !reset
dwc2.io.inq <> dwc.io.deq
dwc2.io.deq.ready := !reset
}
/**
* Generic tester for DataWidthConverterHarness:
* Checks that the output matches the input and selected endianess.
* Uses DataWidthConverterHarness class to check output correctness.
* Tracks incoming data from the data source in expecteds list.
* Whenever output is valid, it is compared to the expecteds,
* mismatches are reported accordingly.
* Does NOT check timing, only correctness of the output values.
**/
class DataWidthConverter_OutputCheck[T <: UInt](m: DataWidthConverterHarness) extends Tester(m, false) {
import scala.util.Properties.{lineSeparator => NL}
var errors: List[String] = List() // error list
// returns binary string for Int, e.g., 0011 for 3, width 4
private def toBinaryString(n: BigInt, width: Int) =
"%%%ds".format(width).format(n.toString(2)).replace(' ', '0')
// check downsizing mode (inWidth > outWidth)
def checkDownsizing() = {
var curr_val = 0
var curr_byt = if (m.dwc.littleEndian) 0 else m.dwc.ratio - 1
var curr_idx = 0
while (curr_idx < m.dsrc.size) {
if (peek(m.dwc.io.deq.valid) > 0) {
val v = m.dwc.io.deq.bits
//println("read %d (0b%s)".format(peek(v).toInt, Integer.toBinaryString(peek(v).toInt)))
curr_val += peek(v).toInt << (curr_byt * m.dwc.outWidth)
if (m.dwc.littleEndian)
curr_byt += 1
else
curr_byt -= 1
/** Performs data correctness check. **/
def check() = {
var i = 0
var expecteds: List[BigInt] = List()
def running = peek(m.dsrc.io.out.valid) > 0 ||
peek(m.dwc.io.inq.valid) > 0 ||
peek(m.dwc.io.deq.valid) > 0 ||
peek(m.dwc2.io.inq.valid) > 0 ||
peek(m.dwc2.io.deq.valid) > 0
while (running) {
// scan output element and add to end of expected list
if (peek(m.dsrc.io.out.valid) > 0 && peek(m.dwc.io.inq.ready) > 0) {
val e = peek(m.dsrc.io.out.bits)
expecteds = expecteds :+ e
println ("adding expected value: %d (%s)".format(e, toBinaryString(e, m.dwc.inWidth)))
}
if (curr_byt == m.dwc.ratio || curr_byt == -1) {
if (curr_val != curr_idx) {
errors = "element #%d should be %d (%d, 0b%s)".format(
curr_idx,
curr_idx,
curr_val,
Integer.toBinaryString(curr_val)
) :: errors
// check output element: must match head of expecteds
if (peek(m.dwc2.io.deq.valid) > 0 && peek(m.dwc2.io.deq.ready) > 0) {
val v = peek(m.dwc2.io.deq.bits)
if (expecteds.isEmpty) {
val errmsg = "received value output value %d (%s), but none expected yet".format(
v, toBinaryString(v, m.dwc.inWidth))
println (errmsg)
expect(false, errmsg)
} else {
if (v == expecteds.head) {
println ("element #%d ok!".format(i))
} else {
val errmsg = "element #%d wrong: expected %d (%s), found %d (%s)".format(
i, expecteds.head, toBinaryString(expecteds.head, m.dwc.inWidth),
v, toBinaryString(v, m.dwc.inWidth))
println (errmsg)
expect(v == expecteds.head, errmsg)
}
expecteds = expecteds.tail
}
//println("read whole: %d (0b%s)".format(curr_val, Integer.toBinaryString(curr_val)))
curr_idx += 1
curr_val = 0
if (m.dwc.littleEndian)
curr_byt = 0
else
curr_byt = m.dwc.ratio - 1
i += 1
}
step(1)
}
}
// check upsizing mode (inWidth < outWidth)
def checkUpsizing() = {
//println("size = %d, ratio = %d".format(m.dsrc.size, m.dwc.ratio))
var received: List[BigInt] = List() // received words
var cc: Int = m.dsrc.size * m.dwc.ratio * 100 // upper bound
// println("cc = %d, %d".format(cc, m.dsrc.size / m.dwc.ratio))
while (received.length < m.dsrc.size / m.dwc.ratio && cc > 0) {
if (peek(m.dwc.io.deq.valid) != 0) {
val v = peek(m.dwc.io.deq.bits)
received ++= List(v)
println("received: 0x%x (%s)".format(v, toBinaryString(v, m.dwc.outWidth)))
}
step(1)
cc -= 1
// advance sim
step (1)
}
// now split into its constituents
val cs = received map (d => toBinaryString(d, m.dwc.outWidth)) map { s =>
for (i <- 0 until s.length / m.dwc.inWidth)
yield Integer.parseInt(s.drop(i * m.dwc.inWidth).take(m.dwc.inWidth), 2)
}
// order constituents, flatten into error list
val es = cs map (x => if (m.dwc.littleEndian) x else x.reverse) reduce (_++_) zip (m.dsrc.ds map (v => peek(v).toInt))
// convert mismatches into errors
errors = (for (i <- 0 until es.length if es(i)._1 != es(i)._2)
yield "element #%d: expected 0x%x (%s), found 0x%x (%s)".format(
i,
es(i)._2,
toBinaryString(es(i)._2, m.dwc.inWidth),
es(i)._1,
toBinaryString(es(i)._1, m.dwc.inWidth)
)).toList
}
reset(10) // reset for 10 cycles
if (m.dwc.inWidth > m.dwc.outWidth)
checkDownsizing()
else
checkUpsizing()
check()
step (20) // settle output
}
assertTrue (("all elements should match, errors: " :: errors).mkString(NL), errors.length == 0)
/**
* Checks timing assumptions:
* Cores must be capable of handling a continuous stream of data, i.e.,
* output stream must always be continuous, if the input stream is.
* This class checks this assumption by stepping through two data elems
* in a continuous fashion, then inserts a short pause and repeats.
* Does NOT check validity of the data, see other checks above.
**/
class DataWidthConverter_DataMustBeAvailableImmediately(m: DataWidthConverter) extends Tester(m, true) {
val v = new java.math.BigInteger("1" * m.inWidth, 2)
reset (10)
expect(peek(m.io.inq.ready) != 0, "must be ready immediately after reset")
for (_ <- 0 to 1) {
poke(m.io.inq.bits, v)
poke(m.io.inq.valid, true)
poke(m.io.deq.ready, true)
if (m.inWidth > m.outWidth) {
for (i <- 1 to m.ratio) {
step (1)
expect(peek(m.io.deq.valid) != 0, "nibble #%d must be ready after one cycle".format(i))
if (i != m.ratio)
expect (peek(m.io.inq.ready) == 0, "input cannot be ready at nibble #%d".format(i))
}
expect(peek(m.io.inq.ready) != 0, "must be ready immediately after first word was consumed")
poke(m.io.inq.bits, 0)
for (i <- 1 to m.ratio) {
step (1)
expect(peek(m.io.deq.valid) != 0, "nibble #%d must be ready after one cycle".format(i))
if (i != m.ratio)
expect (peek(m.io.inq.ready) == 0, "input cannot be ready at nibble #%d".format(i))
}
poke(m.io.inq.valid, false)
expect(peek(m.io.inq.ready) != 0, "must be ready immediately after first word was consumed")
step(1)
expect(peek(m.io.deq.valid) == 0, "second word must be consumed after one cycle")
} else {
for (i <- 1 to m.ratio) {
step(1)
expect(peek(m.io.inq.ready) != 0, "word #%d must be consumed after one cycle".format(i))
if (i != m.ratio)
expect(peek(m.io.deq.valid) == 0, "output cannot be ready at word #%d".format(i))
}
expect(peek(m.io.deq.valid) != 0, "output must be valid with last word")
expect(peek(m.io.inq.ready) != 0, "input must be ready, when output is dequeued immediately")
poke(m.io.inq.bits, 0)
for (i <- 1 to m.ratio) {
step(1)
expect(peek(m.io.inq.ready) != 0, "word #%d must be consumed after one cycle".format(i))
if (i != m.ratio)
expect(peek(m.io.deq.valid) == 0, "output cannot be ready at word #%d".format(i))
}
expect(peek(m.io.deq.valid) != 0, "output must be valid with last word")
expect(peek(m.io.inq.ready) != 0, "input must be ready, when output is dequeued immediately")
poke(m.io.inq.valid, false)
step(1)
expect(peek(m.io.deq.valid) == 0, "output is invalid immediately after last was consumed")
}
step (5)
}
}
/** Unit test for DataWidthConverter hardware. **/
class DataWidthConverterSuite extends JUnitSuite {
def resize(inWidth: Int, outWidth: Int, littleEndian: Boolean = true) = {
println("testing conversion of %d bit to %d bit, %s ..."
......@@ -125,6 +169,26 @@ class DataWidthConverterSuite extends JUnitSuite {
{ m => new DataWidthConverter_OutputCheck(m) }
}
def dataMustBeAvailableImmediately(inWidth: Int, outWidth: Int, littleEndian: Boolean = true) = {
val endian = if (littleEndian) "le" else "be"
println ("testing immediate data availability (%d -> %d, %s) ...".format(inWidth, outWidth, endian))
val dir = Paths.get("test")
.resolve("DataWidthConverterSuite")
.resolve("%dto%d%s_data".format(inWidth, outWidth, endian))
.toString
chiselMainTest(Array("--genHarness", "--backend", "c", "--vcd", "--targetDir", dir, "--compile", "--test"),
() => Module(new DataWidthConverter(inWidth, outWidth, littleEndian)))
{ m => new DataWidthConverter_DataMustBeAvailableImmediately(m) }
}
// simple test group, can be used for waveform analysis
/*@Test def data16to4le { dataMustBeAvailableImmediately(16, 4, true) }
@Test def data4to16le { dataMustBeAvailableImmediately(4, 16, true) }
@Test def check16to4le { resize(16, 4, true) }
@Test def check4to16le { resize(4, 16, true) }
@Test def check16to4be { resize(16, 4, false) }
@Test def check4to16be { resize(4, 16, false) }*/
// downsizing tests
@Test def check2to1le { resize(2, 1, true) }
@Test def check2to1be { resize(2, 1, false) }
......@@ -146,16 +210,30 @@ class DataWidthConverterSuite extends JUnitSuite {
@Test def check1to2be { resize(1, 2, false) }
@Test def check1to8le { resize(1, 8, true) }
@Test def check1to8be { resize(1, 8, false) }
@Test def check4to16le { resize(4, 16, true) }
@Test def check4to16be { resize(4, 16, false) }
@Test def check8to16le { resize(8, 16, true) }
@Test def check8to16be { resize(8, 16, false) }
@Test def check8to32le { resize(8, 32, true) }
@Test def check8to32be { resize(8, 32, false) }
@Test def check8ot64le { resize(8, 64, true) }
@Test def check8to64be { resize(8, 64, false) }
@Test def check32ot64le { resize(32, 64, true) }
@Test def check32to64be { resize(32, 64, false) }
// timing behavior checks
@Test def data2to1le { dataMustBeAvailableImmediately(2, 1, true) }
@Test def data2to1be { dataMustBeAvailableImmediately(2, 1, false) }
@Test def data8to1le { dataMustBeAvailableImmediately(8, 1, true) }
@Test def data8to1be { dataMustBeAvailableImmediately(8, 1, false) }
@Test def data16to4le { dataMustBeAvailableImmediately(16, 4, true) }
@Test def data16to4be { dataMustBeAvailableImmediately(16, 4, false) }
@Test def data16to8le { dataMustBeAvailableImmediately(16, 8, true) }
@Test def data16to8be { dataMustBeAvailableImmediately(16, 8, false) }
@Test def data32to8le { dataMustBeAvailableImmediately(32, 8, true) }
@Test def data32to8be { dataMustBeAvailableImmediately(32, 8, false) }
@Test def data64ot8le { dataMustBeAvailableImmediately(64, 8, true) }
@Test def data64to8be { dataMustBeAvailableImmediately(64, 8, false) }
@Test def data64ot32le { dataMustBeAvailableImmediately(64, 32, true) }
@Test def data64to32be { dataMustBeAvailableImmediately(64, 32, false) }
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment