Commit e23188d9 authored by Jens Korinth's avatar Jens Korinth
Browse files

Squashed 'miscutils/' content from commit 0fec184f

git-subtree-dir: miscutils
git-subtree-split: 0fec184f
parents
*.log
*.jou
.Xil
/target/
/project/
/test/
/ip/
scalaVersion := "2.11.7"
libraryDependencies += "edu.berkeley.cs" %% "chisel" % "latest.release"
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test"
libraryDependencies += "org.scalatest" %% "scalatest" % "2.2.6" % "test"
libraryDependencies += "com.typesafe.play" %% "play-json" % "2.5.4"
parallelExecution in Test := false
testForkedParallel in Test := false
package chisel.miscutils
import Chisel._
/**
* DataWidthConverter converts the data width of a Queue.
* Output is provided via a Queue, with increased or decreased
* data rate, depending on the direction of the conversion.
* Note: This would be much more useful, if the two Queues
* could use different clocks, but multi-clock support
* in Chisel is currently unstable.
* @param inWidth Data width of input Decoupled (bits).
* @param outWidth Data width of output Decoupled (bits); must
* be integer multiples of each other.
* @param littleEndian if inWidth > outWidth, determines
* the order of the nibbles (low to high)
**/
class DataWidthConverter(
val inWidth: Int,
val outWidth: Int,
val littleEndian: Boolean = true
) extends Module {
require (inWidth > 0, "inWidth must be > 0")
require (outWidth > 0, "inWidth must be > 0")
require (inWidth != outWidth, "inWidth (%d) must be different from outWidth (%d)"
.format(inWidth, outWidth))
require (inWidth % outWidth == 0 || outWidth % inWidth == 0,
"inWidth (%d) and outWidth (%d) must be integer multiples of each other"
.format(inWidth, outWidth))
val io = new Bundle {
val inq = Decoupled(UInt(width = inWidth)).flip()
val deq = Decoupled(UInt(width = outWidth))
}
val ratio: Int = if (inWidth > outWidth) inWidth / outWidth else outWidth / inWidth
val d_w = if (inWidth > outWidth) inWidth else outWidth // data register width
if (inWidth > outWidth)
downsize()
else
upsize()
private def upsize() = {
val i = Reg(UInt(width = log2Up(ratio + 1)))
val d = Reg(UInt(width = outWidth))
io.inq.ready := !reset && (i =/= UInt(0) || (io.inq.valid && io.deq.ready))
io.deq.bits := d
io.deq.valid := !reset && i === UInt(0)
when (reset) {
i := UInt(ratio)
d := UInt(0)
}
.otherwise {
when (io.inq.ready && io.inq.valid) {
if (littleEndian)
d := Cat(io.inq.bits, d) >> UInt(inWidth)
else
d := (d << UInt(inWidth)) | io.inq.bits
i := i - UInt(1)
}
when (io.deq.valid && io.deq.ready) {
i := Mux(io.inq.valid, UInt(ratio - 1), UInt(ratio))
}
}
}
private def downsize() = {
val i = Reg(UInt(width = log2Up(ratio + 1)))
val d = Reg(UInt(width = inWidth))
io.inq.ready := !reset && (i === UInt(0) || (i === UInt(1) && io.deq.ready))
if (littleEndian)
io.deq.bits := d(outWidth - 1, 0)
else
io.deq.bits := d(inWidth - 1, inWidth - outWidth)
io.deq.valid := !reset && i > UInt(0)
when (reset) {
i := UInt(0)
d := UInt(0)
}
.otherwise {
when (i > UInt(0) && io.deq.ready) {
if (littleEndian)
d := d >> UInt(outWidth)
else
d := d << UInt(outWidth)
i := i - UInt(1)
}
when (io.inq.ready && io.inq.valid) {
d := io.inq.bits
i := UInt(ratio)
}
}
}
}
package chisel.miscutils
import Chisel._
/**
* Interface for DecoupledDataSource.
**/
class DecoupledDataSourceIO[T <: Data](gen: T) extends Bundle {
val out = Decoupled(gen.cloneType)
}
/**
* Data source providing fixed data via Decoupled interface.
* Provides the data given via Decoupled handshakes; if repeat
* is true, data is wrapped around.
* @param gen Type.
* @param size Total number of elements.
* @param data Function providing data for each index
* @param repeat If true, will always have data via wrap-around,
otherwise valid will go low after data was
consumed.
**/
class DecoupledDataSource[T <: Data](gen: T, val size : Int, data: (Int) => T, val repeat: Boolean = true) extends Module {
println ("DecoupledDataSource: size = %d, repeat = %s".format(size, if (repeat) "true" else "false"))
println(" width = %d".format(log2Up(if (repeat) size else size + 1)))
val ds = for (i <- 0 until size) yield data(i) // evaluate data to array
val io = new DecoupledDataSourceIO(gen) // interface
val i = Reg(UInt(width = log2Up(if (repeat) size else size + 1))) // index
val rom = Vec.tabulate(size)(n => ds(n)) // ROM with data
io.out.bits := rom(i) // current index data
io.out.valid := !reset && i < UInt(size) // valid until exceeded
when (reset) {
i := UInt(0)
}
.otherwise {
if (repeat)
when (io.out.ready && io.out.valid) { i := i + UInt(1) }
else
when (io.out.ready && io.out.valid && i < UInt(size)) { i := i + UInt(1) }
}
}
package chisel.miscutils
import Chisel._
sealed case class Signal(value: Boolean, periods: Int = 1) extends Ordered[Signal] {
import scala.math.Ordered.orderingToOrdered
def compare(that: Signal): Int = periods compare that.periods
}
object SignalGenerator {
type Waveform = List[Signal]
implicit def makeSignal(sd: (Boolean, Int)): Signal = Signal(sd._1, sd._2)
implicit def makeWaveform(ls: List[(Boolean, Int)]): Waveform = ls map makeSignal
}
class SignalGenerator(signals: SignalGenerator.Waveform, useInputAsClock: Boolean = false) extends Module {
require (signals.length > 0, "Waveform must not be empty.")
require (signals map (_.periods > 1) reduce (_&&_),
"All signals must have at least two clock cycles length.")
val io = new Bundle { val v = Bool(OUTPUT); val in = Bool(INPUT) }
val cnts_rom = Vec(signals map (n => UInt(n.periods - 1)))
val vals_rom = Vec(signals map (n => Bool(n.value)))
val cnt = Reg(UInt(width = log2Up(signals.max.periods)))
val curr_idx = Reg(UInt(width = log2Up(signals.length)))
val vreg = Reg(Bool())
io.v := vreg
when (reset) {
curr_idx := UInt(0)
cnt := cnts_rom(0)
vreg := UInt(0)
}
.otherwise {
vreg := vals_rom(curr_idx)
// trigger on either clock or pos input edge
when (if (useInputAsClock) io.in && !RegNext(io.in) else Bool(true)) {
when (cnt === UInt(0)) {
val next_idx = Mux(curr_idx < UInt(signals.length - 1), curr_idx + UInt(1), UInt(0))
curr_idx := next_idx
cnt := cnts_rom(next_idx)
}
.otherwise {
cnt := cnt - UInt(1)
}
}
}
}
package chisel.miscutils
import Chisel._
import org.scalatest.junit.JUnitSuite
import org.junit.Test
import org.junit.Assert._
import scala.math._
import java.nio.file.Paths
class SlowQueue(width: Int, val delay: Int = 10) extends Module {
val io = new Bundle {
val enq = Decoupled(UInt(width = width)).flip
val deq = Decoupled(UInt(width = width))
val dly = UInt(INPUT, width = log2Up(delay))
}
val waiting :: ready :: Nil = Enum(UInt(), 2)
val state = Reg(init = ready)
val wr = Reg(UInt(width = log2Up(delay)))
io.deq.bits := io.enq.bits
io.enq.ready := io.deq.ready && state === ready
io.deq.valid := io.enq.valid && state === ready
when (reset) {
state := ready
}
.otherwise {
when (state === ready && io.enq.ready && io.deq.valid) {
state := waiting
wr := io.dly
}
when (state === waiting) {
wr := wr - UInt(1)
when (wr === UInt(0)) { state := ready }
}
}
}
/**
* 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).
* There's a slow queue in-between to simulate receivers with
* varying speed of consumption.
**/
class DataWidthConverterHarness(inWidth: Int, outWidth: Int, littleEndian: Boolean, delay: Int = 10) extends Module {
val io = new Bundle { val dly = UInt(INPUT, width = log2Up(delay)) }
val dwc = Module(new DataWidthConverter(inWidth, outWidth, littleEndian))
val dsrc = Module(new DecoupledDataSource(UInt(width = inWidth),
Seq(Seq(pow(2, inWidth).toLong, dwc.ratio).max, 10000.toLong).min.toInt,
//n => UInt(n % pow(2, inWidth).toInt + 1, width = inWidth),
n => UInt((scala.math.random * pow(2, inWidth)).toLong, width = inWidth),
repeat = false))
val dwc2 = Module(new DataWidthConverter(outWidth, inWidth, littleEndian))
val slq = Module(new SlowQueue(outWidth, delay))
dwc.io.inq <> dsrc.io.out
slq.io.enq <> dwc.io.deq
slq.io.dly := io.dly
dwc2.io.inq <> slq.io.deq
dwc2.io.deq.ready := Bool(true)
}
/**
* Generic tester for DataWidthConverterHarness:
* 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 DataWidthConverterCorrectnessTester[T <: UInt](m: DataWidthConverterHarness) extends Tester(m, false) {
import scala.util.Properties.{lineSeparator => NL}
// 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')
/** Performs data correctness check. **/
def check() = {
var i = 0
var delay = m.slq.delay - 1
poke (m.slq.io.dly, delay)
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)))
}
// check output element: must match head of expecteds
if (peek(m.dwc2.io.deq.valid) > 0 && peek(m.dwc2.io.deq.ready) > 0) {
// update delay (decreasing with each output)
delay = if (delay == 0) m.slq.delay - 1 else delay - 1
poke(m.io.dly, delay)
// check output
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
}
i += 1
}
// advance sim
step (1)
}
}
reset(10) // reset for 10 cycles
check()
step (20) // settle output
}
/** 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 ..."
.format(inWidth, outWidth, if (littleEndian) "little-endian" else "big-endian"))
val dir = Paths.get("test")
.resolve("DataWidthConverterSuite")
.resolve("%dto%d%s".format(inWidth, outWidth, if (littleEndian) "le" else "be"))
.toString
chiselMainTest(Array("--genHarness", "--backend", "c", "--vcd", "--targetDir", dir, "--compile", "--test"),
() => Module(new DataWidthConverterHarness(inWidth, outWidth, littleEndian)))
{ m => new DataWidthConverterCorrectnessTester(m) }
}
// simple test group, can be used for waveform analysis
/*@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) }
@Test def check64to32be { resize(64, 32, false) }*/
// downsizing tests
@Test def check2to1le { resize(2, 1, true) }
@Test def check2to1be { resize(2, 1, false) }
@Test def check8to1le { resize(8, 1, true) }
@Test def check8to1be { resize(8, 1, false) }
@Test def check16to4le { resize(16, 4, true) }
@Test def check16to4be { resize(16, 4, false) }
@Test def check16to8le { resize(16, 8, true) }
@Test def check16to8be { resize(16, 8, false) }
@Test def check32to8le { resize(32, 8, true) }
@Test def check32to8be { resize(32, 8, false) }
@Test def check64ot8le { resize(64, 8, true) }
@Test def check64to8be { resize(64, 8, false) }
@Test def check64ot32le { resize(64, 32, true) }
@Test def check64to32be { resize(64, 32, false) }
// upsizing tests
@Test def check1to2le { resize(1, 2, true) }
@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) }
}
package chisel.miscutils
import Chisel._
import org.scalatest.junit.JUnitSuite
import org.junit.Test
import org.junit.Assert._
import scala.math._
import java.nio.file.Paths
/**
* 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 DataWidthConverterHarnessFullSpeed(val inWidth: Int, val outWidth: Int, val 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).toLong, dwc.ratio).max, 10000.toLong).min.toInt,
//n => UInt(n % pow(2, inWidth).toInt + 1, width = inWidth),
n => UInt((scala.math.random * pow(2, inWidth)).toLong, width = inWidth),
repeat = false))
val dwc2 = Module(new DataWidthConverter(outWidth, inWidth, littleEndian))
dwc.io.inq <> dsrc.io.out
dwc2.io.inq <> dwc.io.deq
dwc2.io.deq.ready := !reset
}
/**
* Generic tester for DataWidthConverterHarness:
* 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 DataWidthConverterFullSpeed[T <: UInt](m: DataWidthConverterHarnessFullSpeed) extends Tester(m, false) {
import scala.util.Properties.{lineSeparator => NL}
// 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')
/** Performs data correctness at full speed check. **/
def check() = {
var i = 0
var firstOutputReceived = false
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)))
}
// check output element: must match head of expecteds
if (peek(m.dwc2.io.deq.valid) > 0 && peek(m.dwc2.io.deq.ready) > 0) {
firstOutputReceived = true
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
}
i += 1
}
// check: if upsizing, inq may never block
if (m.inWidth < m.outWidth) {
val error = peek(m.dwc.io.inq.ready) == 0 && peek(m.dwc.io.inq.valid) != 0
if (error)
println("ERROR: input queue may never block while input is available")
expect(!error, "upsizing: input queue may not block while input is available")
}
// check: if downsizing, deq must remain valid until end
if (firstOutputReceived && !expecteds.isEmpty && m.inWidth > m.outWidth) {
if (peek(m.dwc.io.deq.valid) == 0)
println("ERROR: output queue must remain valid after first element")
if (peek(m.dwc.io.deq.ready) == 0)
println("ERROR: output queue must remain ready after first element")
expect(peek(m.dwc.io.deq.ready) != 0, "downsizing: output queue must remain ready after first")
expect(peek(m.dwc.io.deq.valid) != 0, "downsizing: output queue must remain valid after first")
}
// advance sim
step (1)
}
}
reset(10) // reset for 10 cycles
check()
step (20) // settle output
}
/** Unit test for DataWidthConverter hardware. **/
class DataWidthConverterSuiteFullSpeed extends JUnitSuite {
def resize(inWidth: Int, outWidth: Int, littleEndian: Boolean = true) = {
println("testing conversion of %d bit to %d bit, %s ..."
.format(inWidth, outWidth, if (littleEndian) "little-endian" else "big-endian"))
val dir = Paths.get("test")
.resolve("dwc_fullspeed")
.resolve("%dto%d%s".format(inWidth, outWidth, if (littleEndian) "le" else "be"))
.toString
chiselMainTest(Array("--genHarness", "--backend", "c", "--vcd", "--targetDir", dir, "--compile", "--test"),
() => Module(new DataWidthConverterHarnessFullSpeed(inWidth, outWidth, littleEndian)))
{ m => new DataWidthConverterFullSpeed(m) }
}
// simple test group, can be used for waveform analysis
/*@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) }
@Test def check64to32be { resize(64, 32, false) }
@Test def check32to64be { resize(32, 64, false) }*/
// downsizing tests
@Test def check2to1le { resize(2, 1, true) }
@Test def check2to1be { resize(2, 1, false) }
@Test def check8to1le { resize(8, 1, true) }
@Test def check8to1be { resize(8, 1, false) }
@Test def check16to4le { resize(16, 4, true) }
@Test def check16to4be { resize(16, 4, false) }
@Test def check16to8le { resize(16, 8, true) }
@Test def check16to8be { resize(16, 8, false) }
@Test def check32to8le { resize(32, 8, true) }
@Test def check32to8be { resize(32, 8, false) }
@Test def check64ot8le { resize(64, 8, true) }
@Test def check64to8be { resize(64, 8, false) }
@Test def check64ot32le { resize(64, 32, true) }
@Test def check64to32be { resize(64, 32, false) }
// upsizing tests
@Test def check1to2le { resize(1, 2, true) }
@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) }