hny-2026/hny2026/test/src/Hny2026.scala
Nikolay Puzanov 6ae8b4fc26 Add continuous mode to HNY2026
Implemented a new `continuous` flag in `HnyConfig` and added a `sent` register in `HNY2026` to track
whether the message has been fully transmitted. When `continuous` is `false`, the module stops
sending after one full cycle.
2026-01-10 17:22:59 +03:00

194 lines
5.6 KiB
Scala

package hny2026.tests
import chisel3._
import chisel3.simulator.scalatest.ChiselSim
import chisel3.simulator.HasSimulator
import svsim.verilator.Backend.CompilationSettings
import org.scalatest.flatspec.AnyFlatSpec
import scala.util.Random
import hny2026._
/**
* Run all tests:
* $ mill hny2026.test
*
* Run only this test:
* $ mill hny2026.test.testOnly hny2026.tests.StrobeGeneratorTest
*/
class StrobeGeneratorTest extends AnyFlatSpec with ChiselSim {
val clockFreq = 10000
// Enable tracing (see: https://github.com/chipsalliance/chisel/discussions/3957)
implicit val verilator: HasSimulator = HasSimulator.simulators
.verilator(verilatorSettings =
CompilationSettings.default.withTraceStyle(
Some(
CompilationSettings.TraceStyle(
kind = CompilationSettings.TraceKind.Vcd))))
/**
* Use Chisel testbench because ChiselSim don't support fork-join constructions.
*
* From documentation (https://www.chisel-lang.org/docs/appendix/migrating-from-chiseltest):
* > ChiselSim also does not currently have any support for fork-join, so any tests
* > using those constructs will need to be rewritten in a single-threaded manner.
*
* @param frameRate
*/
class StrobeGeneratorTB(frameRate: Double) extends Module {
val io = IO(new Bundle {
val duration = Input(UInt(32.W))
val strobeCount = Output(UInt(32.W))
val start = Input(Bool())
val done = Output(Bool())
})
val strobe = StrobeGenerator(HnyConfig(clockFreq, frameRate))
val cycles = RegInit(chiselTypeOf(io.duration), 0.U)
val strobes = RegInit(chiselTypeOf(io.strobeCount), 0.U)
val count = RegInit(false.B)
val done = RegInit(false.B)
io.strobeCount := strobes
io.done := done
when(!done) {
when(count) {
when(cycles === io.duration) {
count := false.B
done := true.B
}
} otherwise {
when(io.start) {
count := true.B
}
}
}
when(count) {
cycles := cycles + 1.U
when(strobe) {
strobes := strobes + 1.U
}
}
}
/**
* TODO: Add description
*
* @param dut
* @param frameRate
* @return
*/
def check(dut: StrobeGeneratorTB)(implicit frameRate: Double): Unit = {
val scale = if ((clockFreq / frameRate).isWhole) 10 else 100
val expect = (frameRate * scale).round.toInt
val duration = (clockFreq * scale).toInt
enableWaves()
dut.io.duration.poke(duration)
dut.io.start.poke(true)
dut.clock.stepUntil(dut.io.done, 1, duration + 10)
val strobes = dut.io.strobeCount.peek().litValue
println(s"$strobes strobes counted. Expected [${expect-1}..${expect+1}]")
assert((strobes >= expect - 1) && (strobes <= expect + 1))
}
behavior of "StrobeGenerator"
it should "produce 25 strobes per second" in {
implicit val frameRate = 25.0
simulate(new StrobeGeneratorTB(frameRate))(check)
}
it should "produce 30 strobes per second" in {
implicit val frameRate = 30.0
simulate(new StrobeGeneratorTB(frameRate))(check)
}
it should "produce 30.1 strobes per second" in {
implicit val frameRate = 30.1
simulate(new StrobeGeneratorTB(frameRate))(check)
}
it should "produce 30.4 strobes per second" in {
implicit val frameRate = 30.4
simulate(new StrobeGeneratorTB(frameRate))(check)
}
}
/**
* Run only this test:
* $ mill hny2026.test.testOnly hny2026.tests.CharSenderTest
*/
class CharSenderTest extends AnyFlatSpec with ChiselSim {
val cfg = HnyConfig(1000, 25)
val dataLength = 10
// Enable tracing (see: https://github.com/chipsalliance/chisel/discussions/3957)
implicit val verilator: HasSimulator = HasSimulator.simulators
.verilator(verilatorSettings =
CompilationSettings.default.withTraceStyle(
Some(
CompilationSettings.TraceStyle(
kind = CompilationSettings.TraceKind.Vcd))))
behavior of "CharSender"
it should s"send $dataLength random bytes" in {
simulate(new CharSender(cfg)) { dut =>
enableWaves()
val data = List.fill(dataLength)(Random.nextInt(Math.pow(2, cfg.dataWidth).toInt))
data.foreach { x =>
dut.io.data.bits.poke(x)
dut.io.data.valid.poke(true.B)
dut.clock.step()
dut.clock.stepUntil(dut.io.data.ready, 1, 1000)
dut.io.data.valid.poke(false.B)
}
dut.clock.step((cfg.clockFreq / cfg.frameRate * cfg.dataWidth * 2).toInt)
println("See results in the wave diagram.")
}
}
}
/**
* Run only this test:
* $ mill hny2026.test.testOnly hny2026.tests.HNY2026Test
*/
class HNY2026Test extends AnyFlatSpec with ChiselSim {
val cfg = HnyConfig(1000, 25)
val str = "Hello!"
// Enable tracing (see: https://github.com/chipsalliance/chisel/discussions/3957)
implicit val verilator: HasSimulator = HasSimulator.simulators
.verilator(verilatorSettings =
CompilationSettings.default.withTraceStyle(
Some(
CompilationSettings.TraceStyle(
kind = CompilationSettings.TraceKind.Vcd))))
behavior of "HNY2026"
it should s"send string '$str' in non countinous mode" in {
simulate(new HNY2026(cfg.copy(continuous = false), str)) { dut =>
enableWaves()
dut.clock.step((cfg.clockFreq / cfg.frameRate * cfg.dataWidth * str.length() * 2).toInt)
println("See results in the wave diagram.")
}
}
it should s"send string '$str' in countinous mode" in {
simulate(new HNY2026(cfg.copy(continuous = true), str)) { dut =>
enableWaves()
dut.clock.step((cfg.clockFreq / cfg.frameRate * cfg.dataWidth * str.length() * 2).toInt)
println("See results in the wave diagram.")
}
}
}