chiselchiseltest

Serializer in Chisel: Register printf dont seem to make sense


I'm trying build a simple rx serializer in Chisel, accepting a 1-bit serial datastream. The assumption is that the datastream clockrate is significantly lower than the internal clockrate.

I am using a couple of registers to hold data and detect external clock rising edge:

// Register to hold the data input
val dataReg = RegNext(io.data_in)

// Register for rising edge detection
def risingedge(x: Bool): Bool = x && !RegNext(x)
val clkRisingEdge = risingedge(io.clk_in)

so I can update buffer when (io.enable && clkRisingEdge) goes high. If so,

val byteData = Reg(UInt(bufferWidth.W))

when(bitIndex === 0.U) {
  byteData := dataReg
  }.otherwise {
    byteData := Cat(byteData((bufferWidth - 2), 0), dataReg)
}

where bufferWidth = 8 bits.

I then apply my test, which injects a 1-bit datastream, clocked in with clock.step(5). And printing dataReg does indeed make sense, bits are correct.

BUT, when I print byteData using printf("byteData: 0x%x\n", byteData), values dont make sense.

I am probably missing something basic here, being a Chisel noob...

EDIT:

I use clkRisingEdge(clk_in) based on as my trigger to pull data from data_in:

class Serializer extends Module {
  val io = IO(new Bundle {
    val enable = Input(Bool())
    val reset = Input(Bool())
    val data_in = Input(Bool()) // asynchronous, "low" clockrate
    val clk_in = Input(Bool()) // asynchronous, "low" clockrate
    val valid = Output(Bool())
  })

  io.valid := false.B // init output

Solution

  • My [possibly incorrect] suspicion is that you are using this rising edge as a condition for the connection to some register(s).

    In Chisel, clocks are generally implicit. The currently in scope clock binds to registers at the definition of the register rather than binding at the point of connection like in Verilog. Modules extending class Module (as opposed to RawModule) include a default clock (called clock). Thus a very simple module that delays an input by 1 cycle is simply:

    class Delay extends Module {
      val in = IO(Input(UInt(8.W)))
      val out = IO(Output(UInt(8.W)))
    
      val delay = RegNext(in)
      out := delay
    }
    

    (Chisel 3.6.0 Scastie of this example: https://scastie.scala-lang.org/8QMHRsChS6KaVC5vQF4Iig)

    Thus there is no reason to do edge detection with a clock, and you really should not use Bool to represent clocks, you should use the Clock type (which intentionally prevents you from doing logic to the clock because logic on clocks is generally ill-advised in synthesis tools).

    It also looks like you are trying to build a ShiftRegister, you could use the Chisel utility ShiftRegisters (the s indicating you get access to all of the bits of the shift register rather than just the oldest bit), you could do something like:

    import chisel3.util.ShiftRegisters
    
    val dataReg = ShiftRegisters(io.data_in, bufferWidth) // Optional additional arguments for enable and reset value
    val byteData = VecInit(dataReg).asUInt
    

    (Chisel 3.6.0 Scastie: https://scastie.scala-lang.org/sLwKdXl2T6W06GKgSoRJTg)

    EDIT:

    After discussion in comments and edit in original question, I think I understand better.

    You're right that you need a Counter to determine when the ShiftRegisters are full (you could alternatively use Valid bits but that would be less efficient in the number of registers for a slight win in amount of logic, usually not worth it).

    Here is a solution to what I think you're trying to do:

    class Serializer(bufferWidth: Int) extends Module {
      val io = IO(new Bundle {
        val enable = Input(Bool())
        val reset = Input(Bool())
        val data_in = Input(Bool()) // asynchronous, "low" clockrate
        val clk_in = Input(Bool()) // asynchronous, "low" clockrate
        val valid = Output(Bool())
        val out = Output(UInt(bufferWidth.W))
      })
      io.valid := false.B // init output
    
      // Register for rising edge detection
      def risingedge(x: Bool): Bool = x && !RegNext(x)
      val clkRisingEdge = risingedge(io.clk_in)
    
      val dataReg = ShiftRegisters(io.data_in, bufferWidth, clkRisingEdge)
      // underscore throws away actual counter value, we only care about "full"
      val (_, full) = Counter(clkRisingEdge, bufferWidth)
      
      val byteData = VecInit(dataReg).asUInt
      io.out := byteData
      io.valid := full
    }
    

    (Chisel 3.6.0 Scastie: https://scastie.scala-lang.org/lBI7shf7SdemfQMkA4IApA)

    You can use your clkRisingEdge as an enable to both the ShiftRegisters and the Counter.

    Now, neither the ShiftRegisters nor the Counter are that complicated so it's probably a worthwhile exercise to implement this without using the built-ins, but this is how I would write what you've described.

    Also, given that the ShiftRegisters use a Vec[Bool], it lowers in Verilog to individual bits rather than a bitvector. This is changing in newer versions of Chisel with firtool, but your original idea using a UInt and indexing can work as a nice alternative implementation that will use a bitvector in the Verilog even in Chisel 3.6.