chisel入门

 

Combinational Logic

  1. Arbiter: The following circuit arbitrates data coming from a FIFO into two parallel processing units. The FIFO and processing elements (PEs) communicate with ready-valid interfaces. Construct the arbiter to send data to whichever PE is ready to receive data, prioritizing PE0 if both are ready to receive data. Remember that the arbiter should tell the FIFO that it’s ready to receive data when at least one of the PEs can receive data. Also, wait for a PE to assert that it’s ready before asserting that the data are valid.
class Arbiter extends Module {
  val io = IO(new Bundle {
    // FIFO
    val fifo_valid = Input(Bool())
    val fifo_ready = Output(Bool())
    val fifo_data  = Input(UInt(16.W))
    
    // PE0
    val pe0_valid  = Output(Bool())
    val pe0_ready  = Input(Bool())
    val pe0_data   = Output(UInt(16.W))
    
    // PE1
    val pe1_valid  = Output(Bool())
    val pe1_ready  = Input(Bool())
    val pe1_data   = Output(UInt(16.W))
  })

  io.fifo_ready := io.pe0_ready | io.pe1_ready
    io.pe0_valid := io.fifo_valid & io.pe0_ready
    io.pe1_valid := io.fifo_valid & !io.pe0_ready & io.pe1_ready
    io.pe0_data := io.fifo_data
    io.pe1_data := io.fifo_data
}
// generate verilog
println(getVerilog(new Arbiter))
// testbench
test(new Arbiter) { c =>
  import scala.util.Random
  val data = Random.nextInt(65536)
  c.io.fifo_data.poke(data.U)
  
  for (i <- 0 until 8) {
    c.io.fifo_valid.poke((((i >> 0) % 2) != 0).B)
    c.io.pe0_ready.poke((((i >> 1) % 2) != 0).B)
    c.io.pe1_ready.poke((((i >> 2) % 2) != 0).B)

    c.io.fifo_ready.expect((i > 1).B)
    c.io.pe0_valid.expect((i == 3 || i == 7).B)
    c.io.pe1_valid.expect((i == 5).B)
    
    if (i == 3 || i ==7) {
      c.io.pe0_data.expect((data).U)
    } else if (i == 5) {
      c.io.pe1_data.expect((data).U)
    }
  }
}
  1. adder: a parameterized adder that can either saturate the output when overflow occurs, or truncate the results (i.e. wrap around). The parameter we pass into it is called saturate and has type Scala Boolean. This is not a Chisel Bool. So, we’re not creating a single hardware adder that can either saturate or truncate, but rather we’re creating a generator that produces either a saturating hardware adder or a truncating hardware adder. ```scala class ParameterizedAdder(saturate: Boolean) extends Module { val io = IO(new Bundle { val in_a = Input(UInt(4.W)) val in_b = Input(UInt(4.W)) val out = Output(UInt(4.W)) })

    val sum = io.in_a +& io.in_b if (saturate) { io.out := Mux(sum > 15.U, 15.U, sum) } else { io.out := sum } }

for (saturate <- Seq(true, false)) { test(new ParameterizedAdder(saturate)) { c => // 100 random tests val cycles = 100 import scala.util.Random import scala.math.min for (i <- 0 until cycles) { val in_a = Random.nextInt(16) val in_b = Random.nextInt(16) c.io.in_a.poke(in_a.U) c.io.in_b.poke(in_b.U) if (saturate) { c.io.out.expect(min(in_a + in_b, 15).U) } else { c.io.out.expect(((in_a + in_b) % 16).U) } }

// ensure we test saturation vs. truncation
c.io.in_a.poke(15.U)
c.io.in_b.poke(15.U)
if (saturate) {
  c.io.out.expect(15.U)
} else {
  c.io.out.expect(14.U)
}   } } ```
  1. Finite State Machine ```scala // life gets hard-er class GradLife extends Module { val io = IO(new Bundle { val state = Input(UInt(2.W)) val coffee = Input(Bool()) val idea = Input(Bool()) val pressure = Input(Bool()) val nextState = Output(UInt(2.W)) })

val idle :: coding :: writing :: grad :: Nil = Enum(4) io.nextState := idle
when(io.state === idle) { when(io.coffee) {io.nextState := coding} .elsewhen(io.pressure) {io.nextState := writing} .otherwise {io.nextState := idle} }.elsewhen(io.state === coding) { when(io.pressure | io.idea) {io.nextState := writing} .otherwise {io.nextState := coding} }.elsewhen(io.state === writing) { when(io.pressure) {io.nextState := grad} .otherwise {io.nextState := writing} } } println(getVerilog(new GradLife))

// Test test(new GradLife) { c => // verify that the hardware matches the golden model for (state <- 0 to 3) { for (coffee <- List(true, false)) { for (idea <- List(true, false)) { for (pressure <- List(true, false)) { c.io.state.poke(state.U) c.io.coffee.poke(coffee.B) c.io.idea.poke(idea.B) c.io.pressure.poke(pressure.B) } } } } }


## Sequential Logic
1. Parameterized Shift Register
```scala
class MyOptionalShiftRegister(val n: Int, val init: BigInt = 1) extends Module {
  val io = IO(new Bundle {
    val en  = Input(Bool())
    val in  = Input(Bool())
    val out = Output(UInt(n.W))
  })

  val state = RegInit(init.U(n.W))
  val nextState = (state << 1) | io.in
  when (io.en) {
    state  := nextState
  }
  io.out := state
}
// test different depths
for (i <- Seq(3, 4, 8, 24, 65)) {
  println(s"Testing n=$i")
  test(new MyOptionalShiftRegister(n = i)) { c =>
    val inSeq = Seq(0, 1, 1, 1, 0, 1, 1, 0, 0, 1)
    var state = c.init
    var i = 0
    c.io.en.poke(true.B)
    while (i < 10 * c.n) {
      // poke in repeated inSeq
      val toPoke = inSeq(i % inSeq.length)
      c.io.in.poke((toPoke != 0).B)
      // update expected state
      state = ((state * 2) + toPoke) & BigInt("1"*c.n, 2)
      c.clock.step(1)
      c.io.out.expect(state.U)

      i += 1
    }
  }
}class MyShiftRegister(val init: Int = 1) extends Module {
  val io = IO(new Bundle {
    val in  = Input(Bool())
    val out = Output(UInt(4.W))
  })

  val state = RegInit(UInt(4.W), init.U)
  val nextState := (state << 1) | io.in
  state := nextState
  out := state
}

test(new MyShiftRegister()) { c =>
  var state = c.init
  for (i <- 0 until 10) {
    // poke in LSB of i (i % 2)
    c.io.in.poke(((i % 2) != 0).B)
    // update expected state
    state = ((state * 2) + (i % 2)) & 0xf
    c.clock.step(1)
    c.io.out.expect(state.U)
  }
}