Skip to main content

# Serialization Cookbook

- Why do I need to serialize Modules
- How do I serialize Modules with `SerializableModuleGenerator`

## 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()
    )