Skip to main content

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:

  1. the SerializableModule should not be inner class, since the outer class is a parameter to it;
  2. the SerializableModule has and only has one parameter with SerializableModuleParameter as its type.
  3. 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:

  1. user can use SerializableModuleGenerator(module: class[SerializableModule], parameter: SerializableModuleParameter) to auto serialize a Module and its parameter.
  2. user can nest SerializableModuleGenerator in other serializable parameters to represent a relative large parameter.
  3. 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()
)