spinal.lib.experimental.com.serial.SerialLink.scala Maven / Gradle / Ivy
package spinal.lib.experimental.com.serial
import spinal.core._
import spinal.lib._
object SerialLinkConst {
def cData = B"d01"
def cClose = B"d02"
def cOpen = B"d03"
def cIsClose = B"d04"
def cIsOpen = B"d05"
def chunkDataSizeMax = 32
def bitsWidth = 8
}
class SerialLinkRxToTx extends Bundle {
val close = Bool()
val open = Bool()
val miss = Bool()
val rxPtr = UInt(16 bit)
val otherRxPtr = Flow(UInt(16 bit))
}
object SerialLinkTxState extends SpinalEnum {
val eNewFrame, eMyPtr0, eMyPtr1, eMessagePtr0, eMessagePtr1, eData = newElement()
}
class SerialLinkTx(bufferSize: Int, burstSize: Int, resendTimeoutLimit: Int) extends Component {
assert(isPow2(bufferSize))
import SerialLinkConst._
import SerialLinkTxState._
val io = new Bundle {
val input = slave Stream (Bits(bitsWidth bit))
val output = master Stream Fragment(Bits(bitsWidth bit))
val rxToTx = in(new SerialLinkRxToTx)
}
//Timeouts
val resendTimeout = Timeout(resendTimeoutLimit)
val aliveTimeout = Timeout(resendTimeoutLimit)
//tx buffer management
val buffer = new Area {
val ram = Mem(Bits(bitsWidth bit), bufferSize)
//Ring/Fifo ptr
val writePtr = Counter(1 << 17)
val readPtr = Counter(1 << 17)
val syncPtr = Reg(UInt(17 bit)) //Last RX ack from the remote controller
when(syncPtr === writePtr){
resendTimeout.clear()
}
//Capacity flags
val empty = writePtr(log2Up(bufferSize) downto 0) === readPtr(log2Up(bufferSize) downto 0)
val full = writePtr(log2Up(bufferSize) downto 0) === (readPtr(log2Up(bufferSize) downto 0) ^ bufferSize)
//Fill the buffer from upper layer data
io.input.ready := !full
when(io.input.fire) {
ram.write(writePtr.resized, io.input.payload)
writePtr.increment()
}
//manage syncPtr from rxToTx notification port
when(io.rxToTx.otherRxPtr.fire) {
when(syncPtr =/= io.rxToTx.otherRxPtr.payload) {
resendTimeout.clear()
}
syncPtr := io.rxToTx.otherRxPtr.payload.resized
}
//read managment
val readFlag = False
val readPort = ram.readSync(readPtr.resized,readFlag)
readPtr.willIncrement := readFlag
}
//Close/Open TX flags
val sendClosingNotification = RegInit(True)
val sendOpeningNotification = RegInit(False)
sendClosingNotification := io.rxToTx.close | io.rxToTx.close
sendOpeningNotification := io.rxToTx.open | io.rxToTx.open
val otherWindow = 32 //TODO
//Main state machine, it handle the protocole
val statemachine = new Area {
val state = RegInit(eNewFrame)
val dataBuffer = Reg(Bits(8 bit))
val txDataLeft = Reg(UInt(log2Up(burstSize+1) bit))
val txDataLeftIsZero = txDataLeft === 0
val isLastData = Reg(Bool())
val isOpen = RegInit(False)
//State before the connection opening
when(!isOpen) {
buffer.writePtr.valueNext := 0
buffer.readPtr.valueNext := 0
buffer.syncPtr := 0
}
io.output.valid := False
io.output.last := False
io.output.fragment := buffer.readPort
when(buffer.readFlag){
txDataLeft := txDataLeft - 1
}
//Statemachine switch case
switch(state) {
is(eNewFrame) {
when(resendTimeout) {
buffer.readPtr.valueNext := buffer.syncPtr
resendTimeout.clear()
}elsewhen(sendClosingNotification) {
io.output.valid := True
io.output.last := True
io.output.fragment := cIsClose
isOpen := False
when(io.output.ready) {
sendClosingNotification := False
}
}elsewhen(sendOpeningNotification) {
io.output.valid := True
io.output.last := True
io.output.fragment := cIsOpen
isOpen := True
when(io.output.ready) {
sendOpeningNotification := False
}
}elsewhen(isOpen && (!buffer.empty || aliveTimeout)) {
io.output.valid := True
io.output.fragment := cData
when(io.output.ready) {
state := eMyPtr0
val otherRxBufferFreeSpace = otherWindow - (buffer.readPtr - buffer.syncPtr)
txDataLeft := Mux[UInt](burstSize < otherRxBufferFreeSpace,burstSize,burstSize).resized
}
}
}
is(eMyPtr0) {
io.output.valid := True
io.output.fragment := B(io.rxToTx.rxPtr(7 downto 0))
dataBuffer := B(io.rxToTx.rxPtr(15 downto 8))
when(io.output.ready) {
state := eMyPtr1
}
}
is(eMyPtr1) {
io.output.valid := True
io.output.fragment := dataBuffer
when(buffer.empty) {
io.output.last := True
when(io.output.ready) {
state := eNewFrame
}
} otherwise {
when(io.output.ready) {
state := eMessagePtr0
}
}
}
is(eMessagePtr0) {
io.output.valid := True
io.output.fragment := B(buffer.readPtr(7 downto 0))
when(io.output.ready) {
state := eMessagePtr1
}
}
is(eMessagePtr1) {
io.output.valid := True
io.output.fragment := B(buffer.readPtr(15 downto 8))
when(buffer.empty || txDataLeftIsZero) {
io.output.last := True
when(io.output.ready) {
state := eNewFrame
}
} otherwise {
when(io.output.ready) {
state := eData
buffer.readFlag := True
}
}
isLastData := False
}
is(eData) {
io.output.valid := True
when(!txDataLeftIsZero && !buffer.empty && !isLastData) {
buffer.readFlag := io.output.ready
} otherwise {
isLastData := True
io.output.last := True
io.output.fragment := buffer.readPort
when(io.output.ready) {
state := eNewFrame
resendTimeout.clear()
aliveTimeout.clear()
}
}
}
}
}
}
object SerialLinkRxState extends SpinalEnum {
val eType, eOtherPtr0, eOtherPtr1, eMessagePtr0, eMessagePtr1, eData = newElement()
}
class SerialLinkRx extends Component {
import SerialLinkConst._
import SerialLinkRxState._
val io = new Bundle {
val input = slave Stream Fragment(Bits(bitsWidth bit))
val output = master Stream (Bits(bitsWidth bit))
val rxToTx = out(new SerialLinkRxToTx)
}
val softReset = RegInit(True)
val state = RegInit(eType)
val rxPtr = Reg(UInt(16 bit))
val keepData = RegInit(False)
val data = io.input.fragment
val dataOld = RegNextWhen(data, io.input.valid)
io.rxToTx.close := False
io.rxToTx.open := False
io.rxToTx.miss := False
io.rxToTx.otherRxPtr.default(0)
io.output.valid := False
io.output.payload := io.input.fragment
io.input.ready := False
when(io.input.valid) {
switch(state) {
is(eType) {
io.input.ready := True
switch(data) {
is(cClose) {
softReset := True
io.rxToTx.close := True
}
is(cOpen) {
softReset := False
rxPtr := 0
io.rxToTx.otherRxPtr.push(0)
io.rxToTx.open := True
}
is(cData) {
state := eOtherPtr0
keepData := True
}
}
}
is(eOtherPtr0) {
io.input.ready := True
state := eOtherPtr1
}
is(eOtherPtr1) {
io.input.ready := True
io.rxToTx.otherRxPtr.push(U(data ## dataOld))
state := eMessagePtr0
}
is(eMessagePtr0) {
when(rxPtr(7 downto 0) =/= U(data)) {
keepData := False
io.rxToTx.miss := True
}
state := eMessagePtr1
io.input.ready := True
}
is(eMessagePtr1) {
when(rxPtr(15 downto 8) =/= U(data)) {
keepData := False
io.rxToTx.miss := keepData
}
state := eData
io.input.ready := True
}
is(eData) {
io.output.valid := io.input.valid && keepData
io.input.ready := io.output.ready
}
}
}
when(io.output.fire) {
rxPtr := rxPtr + 1
}
when(io.input.isLast && io.input.ready) {
state := eType
}
io.rxToTx.rxPtr := rxPtr
}