Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

*: Add serial #41

Merged
merged 6 commits into from
May 12, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions doc/Serial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Serial

串口目前仅考虑实现 9600Hz,8N1,无流控的配置。

串口由输入部分和输出部分组成。

Todo: 支持相关异常(缓冲区空/满)和中断。

## Receive

串口的输入部分负责从外界经过 `rxWire` 接收到的数据送读取到内部环形缓冲区中。

串口的输入部分内部有大小可配置的缓冲区,`rxWire` 上接收到的数据会先存在环形缓冲区中,并异步地由内部总线读取。

可以从总线上读取状态寄存器,来获得目前缓冲区的使用状况。

## Transmit

串口的输出部分负责将由 CPU 等送来的数据发送经过 `txWire` 发送到外界。

串口的输出部分内部有大小可配置的缓冲区,内部总线上送来的数据会先存在环形缓冲区中,并异步地发送到外界。

此外可以从总线上读取状态寄存器,来获得目前缓冲区的使用状况。

## 地址和寄存器

### 基地址

串口的基地址为 0x10014_000。

Transmit 部分和 Receive 部分使用自低位数起第三位进行区分。

#### Receive 部分

Receive 部分的基地址为 0x100140_00。

##### Receive FIFO 寄存器

Receive FIFO 寄存器的地址为 0x10014000。

该寄存器为只读。

这个寄存器记录了目前串口的输入部分等待被读取的值,长度为 1 字节。

读取此寄存器会自增环形缓冲区中的读取 index,使其指向下一个待读取的值。

##### Receive 状态寄存器

Receive 状态寄存器的地址为 0x10014001。

该寄存器为只读。

这个寄存器记录了目前串口的输入部分的状态,包括:
- 环形缓冲区写入 index,指向下一个要由 rxWire 写入的值,从最低的第 0 个 bit 开始,长度为 n bit
- 环形缓冲区读取 index,指向下一个等待被内部总线读取的值,从最低的第 n 个 bit 开始,长度为 n bit

这里的 n 是可配置的,是 “存下最大的 index 需要的 bit 数”,最大的 index 需要是 2 的整数次幂。

#### Transmit 部分

Transmit 部分的基地址为 0x100141_00。

##### Transmit FIFO 寄存器

Transmit FIFO 寄存器的地址为 0x10014100。

该寄存器为只写。

这个寄存器用于向串口的输出部分发送数据,长度为 1 字节。

写入此寄存器会自增环形缓冲区中的读取 index,使其指向下一个待输出的值。

##### Transmit 状态寄存器

Transmit 状态寄存器的地址为 0x10014101。

该寄存器为只读。

这个寄存器记录了目前串口的输出部分的状态,包括:
- 环形缓冲区读取 index,指向下一个由内部总线写入的值,从最低的第 0 个 bit 开始,长度为 n bit
- 环形缓冲区写入 index,指向下一个要由 txWire 输出的值,从最低的第 n 个 bit 开始,长度为 n bit

这里的 n 是可配置的,是 “存下最大的 index 需要的 bit 数”,最大的 index 需要是 2 的整数次幂。



19 changes: 19 additions & 0 deletions src/main/scala/DataBus.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,20 @@ class DataBus extends Module {
val DATA_END_ADDRESS = "h90000000"
val GPIO_BASE_ADDRESS = "h10012000"
val GPIO_END_ADDRESS = "h10013000"
val SERIAL_BASE_ADDRESS = "h10014000"
val SERIAL_END_ADDRESS = "h10015000"

val io = IO(new DataBusBundle {
val timerInterruptPending = Output(Bool())
val gpioOut = Output(UInt(32.W))
val serialRx = Input(Bool())
val serialTx = Output(Bool())
})

val timer = Module(new Timer)
val sram = Module(new ByteAddressedSRAM)
val gpio = Module(new GPIOController)
val serial = Module(new SerialController(25000000,9600,3))

timer.io.readMode := true.B
timer.io.address := 0.U(32.W)
Expand All @@ -41,9 +46,17 @@ class DataBus extends Module {
gpio.io.dataIn := 0.U(32.W)
gpio.io.maskLevel := Mask.NONE

serial.io.readMode := true.B
serial.io.address := 0.U(32.W)
serial.io.dataIn := 0.U(32.W)
serial.io.maskLevel := Mask.NONE

io.dataOut := 0xdead.U(32.W)
io.gpioOut := gpio.io.dataOut

io.timerInterruptPending := timer.io.interruptPending
io.serialTx := serial.io.txWire
serial.io.rxWire := io.serialRx

when(DATA_BASE_ADDRESS.U <= io.address & io.address < DATA_END_ADDRESS.U) {
sram.io.dataIn := io.dataIn
Expand All @@ -57,6 +70,12 @@ class DataBus extends Module {
gpio.io.maskLevel := io.maskLevel
gpio.io.readMode := io.readMode
io.dataOut := gpio.io.dataOut
}.elsewhen(SERIAL_BASE_ADDRESS.U <= io.address & io.address < SERIAL_END_ADDRESS.U) {
serial.io.dataIn := io.dataIn
serial.io.address := io.address - SERIAL_BASE_ADDRESS.U
serial.io.maskLevel := io.maskLevel
serial.io.readMode := io.readMode
io.dataOut := serial.io.dataOut
}.otherwise {
timer.io.dataIn := io.dataIn
timer.io.address := io.address
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/SRAM.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class ByteAddressedSRAM extends Module {
}
is(HALF_WORD) {
// the last 1 bit must be zero, currently we just ignore it
// since the spec says the behaviour in this situation is implementation defined
// since the spec says the behavior in this situation is implementation defined
// todo: either support unaligned write or raise an exception
when(io.readMode) {
io.dataOut := Mux(io.address(1),
Expand All @@ -91,7 +91,7 @@ class ByteAddressedSRAM extends Module {
}
is(WORD) {
// the last 2 bits must be zero, currently we just ignore it
// since the spec says the behaviour in this situation is implementation defined
// since the spec says the behavior in this situation is implementation defined
// todo: either support unaligned write or raise an exception
when(io.readMode) {
io.dataOut := Cat(inner.io.dataOut(3), inner.io.dataOut(2), inner.io.dataOut(1), inner.io.dataOut(0))
Expand Down
206 changes: 206 additions & 0 deletions src/main/scala/SerialController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import chisel3._
import chisel3.util._

object TransitionState extends Enumeration {
val IDLE = "b00".U(2.W)
val START = "b01".U(2.W)
val BITS = "b10".U(2.W)
val PARITY = "b11".U(2.W)
}

object Registers extends Enumeration {
val DATA = "b00".U(2.W)
val STATUS = "b01".U(2.W)
}

object CountDownCalculator {
def countDownBits(maxValue: Int): Int = {
var result = 1;
var currentMax = 1;
while (currentMax < maxValue) {
result += 1;
currentMax <<= 1;
currentMax |= 1;
}
return result;
}
}

class SerialTransmitController(freqIn: Int, freqOut: Int, bufferWidthBits: Int) extends Module {
val io = IO(new DataBusBundle {
val txWire = Output(Bool())
})
val transmitBuffer = Reg(Vec(1 << bufferWidthBits, UInt(8.W)))
val transmitBufferIndexToClient = RegInit(0.U(bufferWidthBits.W))
val transmitBufferIndexFromHost = RegInit(0.U(bufferWidthBits.W))
val statusOutput = Cat(transmitBufferIndexFromHost, transmitBufferIndexToClient)
val byteTransmitting = transmitBuffer(transmitBufferIndexToClient)
val state = RegInit(TransitionState.IDLE)
val transitionCountDown = RegInit(0.U(CountDownCalculator.countDownBits(freqIn / freqOut - 1).W))
val bitTransmittingIndex = Reg(UInt(3.W))

io.txWire := false.B
io.dataOut := 0xdead.U
switch(state) {
is(TransitionState.IDLE) {
io.txWire := true.B
when(transitionCountDown =/= 0.U) {
transitionCountDown := transitionCountDown - 1.U
}.elsewhen(transmitBufferIndexFromHost =/= transmitBufferIndexToClient) {
state := TransitionState.START
transitionCountDown := (freqIn / freqOut - 1).U
}
}
is(TransitionState.START) {
when(transitionCountDown === 0.U) {
io.txWire := false.B
state := TransitionState.BITS
bitTransmittingIndex := 0.U
transitionCountDown := (freqIn / freqOut - 1).U
}.otherwise {
io.txWire := false.B
transitionCountDown := transitionCountDown - 1.U
}
}
is(TransitionState.BITS) {
when(transitionCountDown === 0.U) {
io.txWire := byteTransmitting(bitTransmittingIndex)
when(bitTransmittingIndex === 7.U) {
state := TransitionState.IDLE
transmitBufferIndexToClient := transmitBufferIndexToClient + 1.U
transitionCountDown := (freqIn / freqOut - 1).U
}.otherwise {
bitTransmittingIndex := bitTransmittingIndex + 1.U
transitionCountDown := (freqIn / freqOut - 1).U
}
}.otherwise {
io.txWire := byteTransmitting(bitTransmittingIndex)
transitionCountDown := transitionCountDown - 1.U
}
}
is(TransitionState.PARITY) {
// unimplemented
longfangsong marked this conversation as resolved.
Show resolved Hide resolved
}
}
when(~io.readMode & io.maskLevel =/= Mask.NONE) {
when(io.address(1, 0) === Registers.DATA) {
// todo: raise exception when transmit buffer is full
transmitBuffer(transmitBufferIndexFromHost) := io.dataIn
transmitBufferIndexFromHost := transmitBufferIndexFromHost + 1.U
}
}.otherwise {
when(io.address(1, 0) === Registers.STATUS) {
io.dataOut := statusOutput
}
}
}

class SerialReceiveController(freqIn: Int, freqOut: Int, bufferWidthBits: Int) extends Module {
val io = IO(new DataBusBundle {
val rxWire = Input(Bool())
})
val receiveBuffer = Reg(Vec(1 << bufferWidthBits, UInt(8.W)))
val receiveBufferIndexFromClient = RegInit(0.U(bufferWidthBits.W))
val receiveBufferIndexToHost = RegInit(0.U(bufferWidthBits.W))

val state = RegInit(TransitionState.IDLE)
val sampleCountDown = Reg(UInt(CountDownCalculator.countDownBits(freqIn / freqOut - 1).W))
val byteReceiving = Reg(Vec(8, Bool()))
val bitReceivingIndex = Reg(UInt(3.W))

val increaseReceiveBufferIndexToHost = Reg(Bool())
val holdStatus = Reg(Bool())
val updateBuffer = Reg(Bool())
val fifoOutput = receiveBuffer(receiveBufferIndexToHost)
val statusOutput = Cat(receiveBufferIndexToHost, receiveBufferIndexFromClient)
val dataOutBuffer = Reg(UInt(32.W))
io.dataOut := dataOutBuffer
switch(state) {
is(TransitionState.IDLE) {
when(io.rxWire === 0.U) {
state := TransitionState.START
sampleCountDown := (freqIn / freqOut / 2 - 1).U
}
}
is(TransitionState.START) {
when(sampleCountDown === 0.U) {
sampleCountDown := (freqIn / freqOut - 1).U
bitReceivingIndex := 0.U
state := TransitionState.BITS
}.otherwise {
sampleCountDown := sampleCountDown - 1.U
}
}
is(TransitionState.BITS) {
when(updateBuffer) {
state := TransitionState.IDLE
receiveBuffer(receiveBufferIndexFromClient) := byteReceiving.asUInt
receiveBufferIndexFromClient := receiveBufferIndexFromClient + 1.U
updateBuffer := false.B
}.elsewhen(sampleCountDown === 0.U) {
byteReceiving(bitReceivingIndex) := io.rxWire
sampleCountDown := (freqIn / freqOut - 1).U
when(bitReceivingIndex === 7.U) {
updateBuffer := true.B
}.otherwise {
bitReceivingIndex := bitReceivingIndex + 1.U
}
}.otherwise {
sampleCountDown := sampleCountDown - 1.U
}
}
is(TransitionState.PARITY) {
// unimplemented
longfangsong marked this conversation as resolved.
Show resolved Hide resolved
}
}
when(holdStatus) {
dataOutBuffer := statusOutput
holdStatus := false.B
}.elsewhen(increaseReceiveBufferIndexToHost) {
dataOutBuffer := fifoOutput
receiveBufferIndexToHost := receiveBufferIndexToHost + 1.U
increaseReceiveBufferIndexToHost := false.B
}.elsewhen(io.readMode & io.maskLevel =/= Mask.NONE) {
when(io.address(1, 0) === Registers.DATA) {
dataOutBuffer := fifoOutput
// todo: raise exception when receive buffer is empty
increaseReceiveBufferIndexToHost := true.B
}.elsewhen(io.address(1, 0) === Registers.STATUS) {
dataOutBuffer := statusOutput
holdStatus := true.B
}.otherwise {
dataOutBuffer := 0xef.U(32.W)
}
}.otherwise {
dataOutBuffer := 0xad.U(32.W)
}
}

class SerialController(freqIn: Int, freqOut: Int, bufferWidthBits: Int) extends Module {
val io = IO(new DataBusBundle {
val rxWire = Input(Bool())
val txWire = Output(Bool())
})
val receiveController = Module(new SerialReceiveController(freqIn, freqOut, bufferWidthBits))
val transmitController = Module(new SerialTransmitController(freqIn, freqOut, bufferWidthBits))
receiveController.io.rxWire := io.rxWire
receiveController.io.readMode := io.readMode
receiveController.io.address := io.address(1, 0)

io.txWire := transmitController.io.txWire
transmitController.io.readMode := io.readMode
transmitController.io.address := io.address(1, 0)
when(io.address(2) === 0.U) {
longfangsong marked this conversation as resolved.
Show resolved Hide resolved
receiveController.io.dataIn := io.dataIn
transmitController.io.dataIn := 0xdead.U
io.dataOut := receiveController.io.dataOut
receiveController.io.maskLevel := io.maskLevel
transmitController.io.maskLevel := Mask.NONE
}.otherwise {
receiveController.io.dataIn := 0xdead.U
transmitController.io.dataIn := io.dataIn
io.dataOut := transmitController.io.dataOut
transmitController.io.maskLevel := io.maskLevel
receiveController.io.maskLevel := Mask.NONE
}
}
4 changes: 4 additions & 0 deletions src/main/scala/Top.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import chisel3._
class Top extends Module {
val io = IO(new Bundle {
val gpio = Output(UInt(32.W))
val serialRx = Input(Bool())
val serialTx = Output(Bool())
})
val programROM = Module(new ProgramROM)
val dataBus = Module(new DataBus)
io.gpio := dataBus.io.gpioOut
io.serialTx := dataBus.io.serialTx
dataBus.io.serialRx := io.serialRx
val cpu = Module(new CPU)
dataBus.io.readMode := cpu.io.dataBusBundle.readMode
dataBus.io.address := cpu.io.dataBusBundle.address
Expand Down
4 changes: 3 additions & 1 deletion src/test/scala/DataBusTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ class DataBusTest(addressSpace: DataBus) extends PeekPokeTester(addressSpace) {
(true.B, "h0000bff8".U(32.W), WORD, "h00000000".U(32.W), "h00000008", "0", false),
(true.B, "h0000bff8".U(32.W), WORD, "h00000000".U(32.W), "h00000009", "0", false),
(true.B, "h0000bff8".U(32.W), WORD, "h00000000".U(32.W), "h0000000A", "0", true),
(true.B, "h0000bff8".U(32.W), WORD, "h00000000".U(32.W), "h0000000B", "0", true))
(true.B, "h0000bff8".U(32.W), WORD, "h00000000".U(32.W), "h0000000B", "0", true),
(false.B, "h10014100".U(32.W), WORD, "h000000aa".U(32.W), "0", "0", true),
(true.B, "h10014101".U(32.W), WORD, "h00000004".U(32.W), "0", "0", true))
for ((enable_read, address, maskLevel, dataIn, expectedDataOut, expectedGPIOOut, timerIntPending) <- cases) {
poke(addressSpace.io.readMode, enable_read)
poke(addressSpace.io.address, address)
Expand Down
Loading