Serialization Cookbook
Why do I need to serialize Modules
Chisel provides a very flexible hardware design experience. However, it sometimes becomes too flexible to design a relative big designs, since parameters of module might come from: 1. Global variables; 2. Outer class; 3. Entropies(time, random). It becomes really hard or impossible to describe "how to reproduce this single module?". This forbids doing unit-test for a module generator, and introduces issues in post-synthesis when doing ECO: a change to Module A might lead to change in Module B.
Thus SerializableModuleGenerator
, SerializableModule[T <: SerializableModuleParameter]
and SerializableModuleParameter
are provided to solve these issues.
For any SerializableModuleGenerator
, Chisel can automatically serialize and de-serialize it by adding these constraints:
- the
SerializableModule
should not be inner class, since the outer class is a parameter to it; - the
SerializableModule
has and only has one parameter withSerializableModuleParameter
as its type. - the Module neither depends on global variables nor uses non-reproducible functions(random, time, etc), and this should be guaranteed by user, since Scala cannot detect it.
It can provide these benefits:
- user can use
SerializableModuleGenerator(module: class[SerializableModule], parameter: SerializableModuleParameter)
to auto serialize a Module and its parameter. - user can nest
SerializableModuleGenerator
in other serializable parameters to represent a relative large parameter. - user can elaborate any
SerializableModuleGenerator
into a single module for testing.
How do I serialize Modules with SerializableModuleGenerator
It is pretty simple and illustrated by example below, the GCD
Module with width
as its parameter.
import chisel3._
import chisel3.experimental.{SerializableModule, SerializableModuleGenerator, SerializableModuleParameter}
import upickle.default._
// provide serialization functions to GCDSerializableModuleParameter
object GCDSerializableModuleParameter {
implicit def rwP: ReadWriter[GCDSerializableModuleParameter] = macroRW
}
// Parameter
case class GCDSerializableModuleParameter(width: Int) extends SerializableModuleParameter
// Module
class GCDSerializableModule(val parameter: GCDSerializableModuleParameter)
extends Module
with SerializableModule[GCDSerializableModuleParameter] {
val io = IO(new Bundle {
val a = Input(UInt(parameter.width.W))
val b = Input(UInt(parameter.width.W))
val e = Input(Bool())
val z = Output(UInt(parameter.width.W))
})
val x = Reg(UInt(parameter.width.W))
val y = Reg(UInt(parameter.width.W))
val z = Reg(UInt(parameter.width.W))
val e = Reg(Bool())
when(e) {
x := io.a
y := io.b
z := 0.U
}
when(x =/= y) {
when(x > y) {
x := x - y
}.otherwise {
y := y - x
}
}.otherwise {
z := x
}
io.z := z
}
using write
function in upickle
, it should return a json string:
val j = upickle.default.write(
SerializableModuleGenerator(
classOf[GCDSerializableModule],
GCDSerializableModuleParameter(32)
)
)
// j: String = "{\"parameter\":{\"width\":32},\"generator\":\"repl.MdocSession$MdocApp$GCDSerializableModule\"}"
You can then read from json string and elaborate the Module:
circt.stage.ChiselStage.emitSystemVerilog(
upickle.default.read[SerializableModuleGenerator[GCDSerializableModule, GCDSerializableModuleParameter]](
ujson.read(j)
).module()
)