Skip to content

Commit

Permalink
Fix SPI patch for CI (#30)
Browse files Browse the repository at this point in the history
* Revert "Revert "Add SPI master chip (#28)" (#29)"

This reverts commit 0e112f1.

* spi: Fix formatting

---------

Co-authored-by: Kevin Joly <[email protected]>
  • Loading branch information
Kev-J and Kevin Joly authored Feb 6, 2024
1 parent 0e112f1 commit 5fd9c2c
Show file tree
Hide file tree
Showing 3 changed files with 333 additions and 0 deletions.
27 changes: 27 additions & 0 deletions src/main/scala/chisel/lib/spi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Serial Peripheral Interface (SPI)

Minimalistic version of SPI interface.

Features:
- Modes 00, 01, 10 and 11
- Word length in parameters

Status: Tested in mode 00, 01, 10, 11 with cocotb

# Test

prerequisites:
- cocotb
- cocotbext-spi

* Generate verilog sources
```sbt "runMain chisel.lib.spi.MasterOneCS"```

* Run testbench
```
cd src/test/scala/chisel/lib/spi
make
```

* Display wave
```gtkwave dump.vcd```
130 changes: 130 additions & 0 deletions src/main/scala/chisel/lib/spi/Spi.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
*
* Serial Peripheral Interface (SPI) interface
*
* Author: Kevin Joly ([email protected])
*
*/
package chisel.lib.spi

import chisel3._
import chisel3.experimental.Analog
import chisel3.experimental.ChiselEnum
import chisel3.stage.ChiselStage
import chisel3.util._

class Master(frequency: Int, clkfreq: Int, bsize: Int) extends Module {
val io = IO(new Bundle {
val cpol = Input(Bool())
val cpha = Input(Bool())
val msbfirst = Input(Bool())
val mosi = Output(Bool())
val miso = Input(Bool())
val sclk = Output(Bool())
val din = Decoupled(UInt(bsize.W))
val dout = Flipped(Decoupled(UInt(bsize.W)))
val busy = Output(Bool())
})

object State extends ChiselEnum {
val sIdle, sHalfCycle, sLoad, sShift = Value
}

val state = RegInit(State.sIdle)

val clockPrescaler = (frequency + clkfreq / 2) / clkfreq / 2 - 1
val CLKPRE = clockPrescaler.asUInt(clockPrescaler.W)

val bits = RegInit(0.U((bsize - 1).W))
val regout = RegInit(0.U(bsize.W))
val regin = RegInit(0.U(bsize.W))
val cnt = RegInit(0.U(clockPrescaler.W))

val cpolReg = RegInit(false.B)
val cphaReg = RegInit(false.B)
val msbfirstReg = RegInit(false.B)

switch(state) {
is(State.sIdle) {

cpolReg := io.cpol
cphaReg := io.cpha
msbfirstReg := io.msbfirst

when(io.dout.valid) {
regout := io.dout.bits
bits := bsize.asUInt - 1.U
cnt := CLKPRE
when(cphaReg) {
state := State.sHalfCycle
}.otherwise {
state := State.sLoad
}
}
regin := 0.U
}
is(State.sHalfCycle) {
when(cnt > 0.U) {
cnt := cnt - 1.U
}.otherwise {
cnt := CLKPRE
state := State.sLoad
}
}
is(State.sLoad) {
when(cnt > 0.U) {
cnt := cnt - 1.U
}.elsewhen(bits > 0.U || io.din.ready) {
cnt := CLKPRE
state := State.sShift
when(msbfirstReg) {
regin := Cat(regin(bsize - 2, 0), io.miso)
}.otherwise {
regin := Cat(io.miso, regin(bsize - 1, 1))
}
}
}
is(State.sShift) {
when(cnt > 0.U) {
cnt := cnt - 1.U
}.otherwise {
when(bits > 0.U) {
cnt := CLKPRE
bits := bits - 1.U
state := State.sLoad
}.otherwise {
state := State.sIdle
}
}
}
}

io.dout.ready := state === State.sIdle

io.din.bits := regin
val lastBitShifted = Wire(Bool())
lastBitShifted := (state === State.sShift) && (bits === 0.U)
io.din.valid := lastBitShifted && !RegNext(lastBitShifted)

when(state === State.sLoad) {
io.sclk := cpolReg ^ cphaReg
}.elsewhen(state === State.sShift) {
io.sclk := !(cpolReg ^ cphaReg)
}.otherwise {
io.sclk := cpolReg
}

when(state === State.sIdle) {
io.mosi := false.B
}.elsewhen(msbfirstReg) {
io.mosi := regout(bits)
}.otherwise {
io.mosi := regout(bsize.U - 1.U - bits)
}

io.busy := state =/= State.sIdle
}

object Master extends App {
(new ChiselStage).emitSystemVerilog(new Master(100000000, 10000000, 8), Array("--target-dir", "generated"))
}
176 changes: 176 additions & 0 deletions src/test/scala/chisel/lib/spi/SpiTester.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package chisel.lib.spi

import chisel3._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec

import scala.util.Random

class SpiMasterTest extends AnyFlatSpec with ChiselScalatestTester {

val frequency = 100000000
val clkfreq = 10000000

def transferOneWord(bsize: Int, mode: Int, msbFirst: Boolean, dut: => Master) {
test(dut) { dut =>
val clkStepPerHalfSclk = (frequency + clkfreq / 2) / clkfreq / 2 - 1

val rnd = new Random()
val outputVal = rnd.nextLong() & (Math.pow(2, bsize).toInt - 1)
val inputVal = rnd.nextLong() & (Math.pow(2, bsize).toInt - 1)

var sclkExp = if ((mode == 0) || (mode == 1)) false else true

dut.clock.step()

mode match {
case 0 => {
dut.io.cpol.poke(false.B)
dut.io.cpha.poke(false.B)
}
case 1 => {
dut.io.cpol.poke(false.B)
dut.io.cpha.poke(true.B)
}
case 2 => {
dut.io.cpol.poke(true.B)
dut.io.cpha.poke(false.B)
}
case 3 => {
dut.io.cpol.poke(true.B)
dut.io.cpha.poke(true.B)
}
}

dut.io.msbfirst.poke(msbFirst.B)

dut.clock.step()

// Idle state
dut.io.sclk.expect(sclkExp)
dut.io.mosi.expect(false.B)
dut.io.busy.expect(false.B)

// Send data
dut.io.din.ready.poke(true.B)
dut.io.dout.bits.poke(outputVal.asUInt(bsize.W))

dut.io.dout.valid.poke(true.B)

dut.clock.step()

dut.io.dout.valid.poke(false.B)

if ((mode == 1) || (mode == 3)) {
// Wait half sclk step
for (s <- 0 to clkStepPerHalfSclk) {
dut.io.busy.expect(true.B)
dut.io.sclk.expect(sclkExp)

dut.clock.step()
}

sclkExp = !sclkExp
}

val firstBit = if (msbFirst) bsize - 1 else 0
val lastBit = if (msbFirst) 0 else bsize - 1
val step = if (msbFirst) -1 else 1

for (bit <- firstBit to lastBit by step) {

if ((inputVal & (0x1 << bit)) != 0) {
dut.io.miso.poke(true.B)
} else {
dut.io.miso.poke(false.B)
}

for (s <- 0 to clkStepPerHalfSclk) {
dut.io.busy.expect(true.B)
dut.io.sclk.expect(sclkExp)
if ((outputVal & (0x1 << bit)) != 0) {
dut.io.mosi.expect(true.B)
} else {
dut.io.mosi.expect(false.B)
}

dut.clock.step()
}

sclkExp = !sclkExp

if (bit == lastBit) {
// End of word
dut.io.din.valid.expect(true.B)
dut.io.din.bits.expect(inputVal.asUInt)
} else {
dut.io.din.valid.expect(false.B)
}

for (s <- 0 to clkStepPerHalfSclk) {
dut.io.busy.expect(true.B)
dut.io.sclk.expect(sclkExp)
if ((outputVal & (0x1 << bit)) != 0) {
dut.io.mosi.expect(true.B)
} else {
dut.io.mosi.expect(false.B)
}

dut.clock.step()
}

sclkExp = !sclkExp
}

dut.io.busy.expect(false.B)
}
}

it should "work in mode 0 lsb first" in {
for (bsize <- Seq(4, 8, 16, 32)) {
transferOneWord(bsize, 0, false, new Master(frequency, clkfreq, bsize))
}
}

it should "work in mode 1 lsb first" in {
for (bsize <- Seq(4, 8, 16, 32)) {
transferOneWord(bsize, 1, false, new Master(frequency, clkfreq, bsize))
}
}

it should "work in mode 2 lsb first" in {
for (bsize <- Seq(4, 8, 16, 32)) {
transferOneWord(bsize, 2, false, new Master(frequency, clkfreq, bsize))
}
}

it should "work in mode 3 lsb first" in {
for (bsize <- Seq(4, 8, 16, 32)) {
transferOneWord(bsize, 3, false, new Master(frequency, clkfreq, bsize))
}
}

it should "work in mode 0 msb first" in {
for (bsize <- Seq(4, 8, 16, 32)) {
transferOneWord(bsize, 0, true, new Master(frequency, clkfreq, bsize))
}
}

it should "work in mode 1 msb first" in {
for (bsize <- Seq(4, 8, 16, 32)) {
transferOneWord(bsize, 1, true, new Master(frequency, clkfreq, bsize))
}
}

it should "work in mode 2 msb first" in {
for (bsize <- Seq(4, 8, 16, 32)) {
transferOneWord(bsize, 2, true, new Master(frequency, clkfreq, bsize))
}
}

it should "work in mode 3 msb first" in {
for (bsize <- Seq(4, 8, 16, 32)) {
transferOneWord(bsize, 3, true, new Master(frequency, clkfreq, bsize))
}
}
}

0 comments on commit 5fd9c2c

Please sign in to comment.