blob: a134f9371a8cfa80458927cd8596a06b429c94cc [file] [log] [blame]
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bus
import chisel3._
import chisel3.util._
import freechips.rocketchip.util._
import kelvin.Parameters
class Spi2TLUL(p: Parameters) extends Module {
val tlul_p = new TLULParameters(p)
val io = IO(new Bundle {
val spi = new Bundle {
val clk = Input(Clock())
val csb = Input(Bool())
val mosi = Input(Bool())
val miso = Output(Bool())
}
val tl = new OpenTitanTileLink.Host2Device(new TLULParameters(p))
})
// Synchronize the main asynchronous reset to the SPI clock domain.
val spi_domain_reset = withClock(io.spi.clk) {
val rst_sync = RegNext(RegNext(reset.asBool, true.B), true.B)
rst_sync.asAsyncReset
}
// Combine the main reset with the chip-select reset.
// Reset is active when csb is high (inactive) OR when the main reset is active.
val combined_reset = io.spi.csb || spi_domain_reset.asBool
val (mosi_data_reg, miso_data_reg, miso_valid_reg, bit_count_reg, deq_attempted_reg) =
withClockAndReset(io.spi.clk, combined_reset.asAsyncReset) {
val mosi = RegInit(0.U(8.W))
val miso = RegInit(0.U(8.W))
// Gate MISO output until the first SPI clock cycle to prevent X propagation.
val miso_valid = RegInit(false.B)
val bit_count = RegInit(0.U(3.W))
val deq_attempted = RegInit(false.B)
(mosi, miso, miso_valid, bit_count, deq_attempted)
}
miso_valid_reg := miso_valid_reg || !io.spi.csb
io.spi.miso := Mux(miso_valid_reg, miso_data_reg(7), 0.U)
val spi2tlul_q = Module(new AsyncQueue(UInt(8.W), AsyncQueueParams(depth = 2, safe = false)))
spi2tlul_q.io.enq_clock := io.spi.clk
spi2tlul_q.io.enq_reset := reset.asBool
spi2tlul_q.io.deq_clock := clock
spi2tlul_q.io.deq_reset := reset.asBool
val completed_byte = Cat(mosi_data_reg(6,0), io.spi.mosi)
spi2tlul_q.io.enq.valid := bit_count_reg === 7.U
spi2tlul_q.io.enq.bits := completed_byte
dontTouch(spi2tlul_q.io.enq)
object SpiState extends ChiselEnum {
val sIDLE, sWAIT_WRITE_DATA, sSEND_READ_DATA = Value
}
val spi_state_reg = RegInit(SpiState.sIDLE)
// Define the SPI register map
object SpiRegAddress extends ChiselEnum {
val TL_ADDR_REG_0 = 0x00.U
val TL_ADDR_REG_1 = 0x01.U
val TL_ADDR_REG_2 = 0x02.U
val TL_ADDR_REG_3 = 0x03.U
val TL_LEN_REG = 0x04.U
val TL_CMD_REG = 0x05.U
val TL_STATUS_REG = 0x06.U
val DATA_BUF_PORT = 0x07.U
val TL_WRITE_STATUS_REG = 0x08.U
}
// Physical registers backing the map
val tl_addr_reg = RegInit(VecInit(Seq.fill(4)(0.U(8.W))))
val tl_len_reg = RegInit(0.U(8.W))
// Command and Status registers are handled by the TL FSM, not stored directly here.
val data_buffer = RegInit(VecInit(Seq.fill(16)(0.U(128.W))))
val bulk_read_ptr = RegInit(0.U(8.W)) // Byte pointer into the data buffer
val bulk_write_ptr = RegInit(0.U(8.W)) // Byte pointer for writes
val addr_reg = RegInit(0.U(7.W))
// === TileLink Read FSM ===
object TlReadState extends ChiselEnum {
val sIdle, sSendBeat, sWaitBeatAck, sDone, sError = Value
}
val tl_read_state_reg = RegInit(TlReadState.sIdle)
// Internal registers for the TL transaction
val tl_addr_fsm_reg = RegInit(0.U(32.W))
val tl_len_fsm_reg = RegInit(0.U(8.W))
val tl_beat_count_reg = RegInit(0.U(8.W))
// === TileLink Write FSM ===
object TlWriteState extends ChiselEnum {
val sIdle, sSendBeat, sWaitBeatAck, sDone, sError = Value
}
val tl_write_state_reg = RegInit(TlWriteState.sIdle)
// Internal registers for the TL write transaction
val tl_write_addr_fsm_reg = RegInit(0.U(32.W))
val tl_write_len_fsm_reg = RegInit(0.U(8.W))
val tl_write_beat_count_reg = RegInit(0.U(8.W))
// Wire to detect a write to the command register
val do_write = spi_state_reg === SpiState.sWAIT_WRITE_DATA && spi2tlul_q.io.deq.fire
val tl_cmd_reg_write = do_write && (addr_reg === SpiRegAddress.TL_CMD_REG.asUInt)
val tl_cmd_reg_data = spi2tlul_q.io.deq.bits
val tlul2spi_q = Module(new AsyncQueue(UInt(8.W), AsyncQueueParams.singleton(safe = false)))
tlul2spi_q.io.enq_clock := clock
tlul2spi_q.io.enq_reset := reset.asBool
tlul2spi_q.io.deq_clock := io.spi.clk
tlul2spi_q.io.deq_reset := reset.asBool
// Add queues for TileLink channels to handle backpressure
val tl_a_q = Module(new Queue(new OpenTitanTileLink.A_Channel(tlul_p), 1))
val tl_d_q = Module(new Queue(new OpenTitanTileLink.D_Channel(tlul_p), 1))
io.tl.a <> tl_a_q.io.deq
io.tl.a.bits := RequestIntegrityGen(tlul_p, tl_a_q.io.deq.bits)
tl_d_q.io.enq <> io.tl.d
tlul2spi_q.io.deq.ready := !io.spi.csb && !deq_attempted_reg
// FSM logic
val deq_ready = spi_state_reg === SpiState.sIDLE ||
spi_state_reg === SpiState.sWAIT_WRITE_DATA
spi2tlul_q.io.deq.ready := deq_ready
tlul2spi_q.io.enq.valid := (spi_state_reg === SpiState.sSEND_READ_DATA)
val is_write = spi2tlul_q.io.deq.bits(7)
val state_next = MuxCase(spi_state_reg, Seq(
(spi_state_reg === SpiState.sIDLE && spi2tlul_q.io.deq.fire) ->
Mux(is_write, SpiState.sWAIT_WRITE_DATA, SpiState.sSEND_READ_DATA),
(spi_state_reg === SpiState.sWAIT_WRITE_DATA && spi2tlul_q.io.deq.fire) ->
SpiState.sIDLE,
(spi_state_reg === SpiState.sSEND_READ_DATA && tlul2spi_q.io.enq.fire) ->
SpiState.sIDLE
))
spi_state_reg := state_next
// sIDLE
val addr_reg_next = spi2tlul_q.io.deq.bits(6,0)
addr_reg := Mux(spi_state_reg === SpiState.sIDLE && spi2tlul_q.io.deq.fire,
addr_reg_next,
addr_reg)
// sWAIT_WRITE_DATA
val data = spi2tlul_q.io.deq.bits
val writing_addr_reg = spi_state_reg === SpiState.sWAIT_WRITE_DATA && spi2tlul_q.io.deq.fire
for (i <- 0 until 4) {
tl_addr_reg(i) := Mux(writing_addr_reg && (addr_reg === (SpiRegAddress.TL_ADDR_REG_0 + i.U)), data, tl_addr_reg(i))
}
val writing_len_reg = do_write && addr_reg === SpiRegAddress.TL_LEN_REG.asUInt
tl_len_reg := Mux(writing_len_reg, data, tl_len_reg)
val write_word_index = bulk_write_ptr(7,4)
val write_byte_index = bulk_write_ptr(3,0)
val write_shift = write_byte_index << 3
val write_mask = ~(0xFF.U << write_shift)
val write_old_word = data_buffer(write_word_index)
val write_new_word = (write_old_word & write_mask) | (data << write_shift)
val write_cmd_fire = tl_cmd_reg_write && tl_cmd_reg_data === 2.U
val writing_data_buf = do_write && addr_reg === SpiRegAddress.DATA_BUF_PORT.asUInt
bulk_write_ptr := Mux(write_cmd_fire, 0.U,
Mux(writing_data_buf, bulk_write_ptr + 1.U, bulk_write_ptr))
// sSEND_READ_DATA
val word_index = bulk_read_ptr(7,4)
val byte_index = bulk_read_ptr(3,0)
val selected_word = data_buffer(word_index)
val status_map = Seq(
TlReadState.sIdle.asUInt -> 0x00.U,
TlReadState.sSendBeat.asUInt -> 0x01.U,
TlReadState.sWaitBeatAck.asUInt -> 0x01.U,
TlReadState.sDone.asUInt -> 0x02.U,
TlReadState.sError.asUInt -> 0xFF.U
)
val write_status_map = Seq(
TlWriteState.sIdle.asUInt -> 0x00.U,
TlWriteState.sSendBeat.asUInt -> 0x01.U,
TlWriteState.sWaitBeatAck.asUInt -> 0x01.U,
TlWriteState.sDone.asUInt -> 0x02.U,
TlWriteState.sError.asUInt -> 0xFF.U
)
val read_map = Seq(
SpiRegAddress.TL_ADDR_REG_0.asUInt -> tl_addr_reg(0),
SpiRegAddress.TL_ADDR_REG_1.asUInt -> tl_addr_reg(1),
SpiRegAddress.TL_ADDR_REG_2.asUInt -> tl_addr_reg(2),
SpiRegAddress.TL_ADDR_REG_3.asUInt -> tl_addr_reg(3),
SpiRegAddress.TL_LEN_REG.asUInt -> tl_len_reg,
SpiRegAddress.TL_STATUS_REG.asUInt -> MuxLookup(tl_read_state_reg.asUInt, 0.U)(status_map),
SpiRegAddress.TL_WRITE_STATUS_REG.asUInt ->
MuxLookup(tl_write_state_reg.asUInt, 0.U)(write_status_map),
SpiRegAddress.DATA_BUF_PORT.asUInt -> (selected_word.asUInt >> (byte_index << 3.U))(7,0),
)
tlul2spi_q.io.enq.bits := MuxLookup(addr_reg, 0.U(8.W))(read_map)
val read_cmd_fire = tl_cmd_reg_write && tl_cmd_reg_data === 1.U
val reading_data_buf = spi_state_reg === SpiState.sSEND_READ_DATA &&
tlul2spi_q.io.enq.fire &&
addr_reg === SpiRegAddress.DATA_BUF_PORT.asUInt
bulk_read_ptr := Mux(read_cmd_fire, 0.U,
Mux(reading_data_buf, bulk_read_ptr + 1.U, bulk_read_ptr))
withClock(io.spi.clk) {
mosi_data_reg := Cat(mosi_data_reg(6,0), io.spi.mosi)
bit_count_reg := bit_count_reg + 1.U
deq_attempted_reg := Mux(bit_count_reg === 0.U, true.B, deq_attempted_reg)
miso_data_reg := MuxCase(miso_data_reg, Seq(
(bit_count_reg === 0.U && tlul2spi_q.io.deq.fire) -> tlul2spi_q.io.deq.bits,
(bit_count_reg =/= 0.U) -> Cat(miso_data_reg(6,0), 0.U(1.W)),
))
}
// === TileLink FSM Logic ===
val read_fsm_active = tl_read_state_reg =/= TlReadState.sIdle
val write_fsm_active = tl_write_state_reg =/= TlWriteState.sIdle
tl_a_q.io.enq.valid := MuxCase(false.B, Seq(
read_fsm_active -> (tl_read_state_reg === TlReadState.sSendBeat),
write_fsm_active -> (tl_write_state_reg === TlWriteState.sSendBeat)
))
tl_d_q.io.deq.ready := MuxCase(false.B, Seq(
read_fsm_active -> (tl_read_state_reg === TlReadState.sWaitBeatAck),
write_fsm_active -> (tl_write_state_reg === TlWriteState.sWaitBeatAck)
))
val a_bits = Wire(new OpenTitanTileLink.A_Channel(tlul_p))
a_bits.param := 0.U
a_bits.size := log2Ceil(tlul_p.w).U
a_bits.source := 0.U
a_bits.mask := Fill(tlul_p.w, 1.U)
a_bits.user := 0.U.asTypeOf(a_bits.user)
a_bits.user.instr_type := 9.U // MuBi4False
a_bits.opcode := Mux(write_fsm_active, TLULOpcodesA.PutFullData.asUInt, TLULOpcodesA.Get.asUInt)
a_bits.address := Mux(write_fsm_active,
tl_write_addr_fsm_reg + (tl_write_beat_count_reg << log2Ceil(tlul_p.w)),
tl_addr_fsm_reg + (tl_beat_count_reg << log2Ceil(tlul_p.w)))
a_bits.data := Mux(write_fsm_active, data_buffer(tl_write_beat_count_reg(3,0)), 0.U)
tl_a_q.io.enq.bits := a_bits
val reading_tl = tl_read_state_reg === TlReadState.sWaitBeatAck &&
tl_d_q.io.deq.fire &&
!tl_d_q.io.deq.bits.error
for (i <- 0 until data_buffer.length) {
val write_to_buffer = i.U === write_word_index && writing_data_buf
val read_from_buffer = i.U === tl_beat_count_reg(3,0) && reading_tl
data_buffer(i) := MuxCase(data_buffer(i), Seq(
write_to_buffer -> write_new_word,
read_from_buffer -> tl_d_q.io.deq.bits.data,
))
}
val clear_command = tl_cmd_reg_write && tl_cmd_reg_data === 0.U
// === TileLink Read FSM Logic ===
val tl_state_next = MuxCase(tl_read_state_reg, Seq(
(tl_read_state_reg === TlReadState.sIdle && read_cmd_fire) -> TlReadState.sSendBeat,
(tl_read_state_reg === TlReadState.sSendBeat && tl_a_q.io.enq.fire) ->
TlReadState.sWaitBeatAck,
(tl_read_state_reg === TlReadState.sWaitBeatAck && tl_d_q.io.deq.fire) ->
MuxCase(TlReadState.sSendBeat, Seq(
tl_d_q.io.deq.bits.error -> TlReadState.sError,
(tl_beat_count_reg === tl_len_fsm_reg) -> TlReadState.sDone
)),
(tl_read_state_reg === TlReadState.sDone && clear_command) -> TlReadState.sIdle,
(tl_read_state_reg === TlReadState.sError && clear_command) -> TlReadState.sIdle
))
tl_read_state_reg := tl_state_next
val tl_beat_count_next = Mux(tl_read_state_reg === TlReadState.sWaitBeatAck &&
tl_d_q.io.deq.fire &&
!tl_d_q.io.deq.bits.error,
tl_beat_count_reg + 1.U,
tl_beat_count_reg)
tl_beat_count_reg := Mux(read_cmd_fire, 0.U, tl_beat_count_next)
tl_addr_fsm_reg := Mux(read_cmd_fire, tl_addr_reg.asUInt, tl_addr_fsm_reg)
tl_len_fsm_reg := Mux(read_cmd_fire, tl_len_reg, tl_len_fsm_reg)
// === TileLink Write FSM Logic ===
val tl_write_state_next = MuxCase(tl_write_state_reg, Seq(
(tl_write_state_reg === TlWriteState.sIdle && write_cmd_fire) -> TlWriteState.sSendBeat,
(tl_write_state_reg === TlWriteState.sSendBeat && tl_a_q.io.enq.fire) ->
TlWriteState.sWaitBeatAck,
(tl_write_state_reg === TlWriteState.sWaitBeatAck && tl_d_q.io.deq.fire) ->
MuxCase(TlWriteState.sSendBeat, Seq(
tl_d_q.io.deq.bits.error -> TlWriteState.sError,
(tl_write_beat_count_reg === tl_write_len_fsm_reg) -> TlWriteState.sDone
)),
(tl_write_state_reg === TlWriteState.sDone && clear_command) -> TlWriteState.sIdle,
(tl_write_state_reg === TlWriteState.sError && clear_command) -> TlWriteState.sIdle
))
tl_write_state_reg := tl_write_state_next
val tl_write_beat_count_next = Mux(tl_write_state_reg === TlWriteState.sWaitBeatAck &&
tl_d_q.io.deq.fire &&
!tl_d_q.io.deq.bits.error,
tl_write_beat_count_reg + 1.U,
tl_write_beat_count_reg)
tl_write_beat_count_reg := Mux(write_cmd_fire, 0.U, tl_write_beat_count_next)
tl_write_addr_fsm_reg := Mux(write_cmd_fire, tl_addr_reg.asUInt, tl_write_addr_fsm_reg)
tl_write_len_fsm_reg := Mux(write_cmd_fire, tl_len_reg, tl_write_len_fsm_reg)
}
import _root_.circt.stage.{ChiselStage,FirtoolOption}
import chisel3.stage.ChiselGeneratorAnnotation
import scala.annotation.nowarn
@nowarn
object Spi2TLUL_128_Emitter extends App {
var p = Parameters()
p.lsuDataBits = 128
(new ChiselStage).execute(
Array("--target", "systemverilog") ++ args,
Seq(ChiselGeneratorAnnotation(() => new Spi2TLUL(p))) ++ Seq(FirtoolOption("-enable-layers=Verification"))
)
}