Gowin Vol.3 第2部第4章 リスト5

/**
* mainClockFrequencyHz: メインクロックの周波数 (36MHz)
*/
class EthernetAudioSystem(mainClockFrequencyHz: BigInt) extends RawModule {
val clock = IO(Input(Clock())) // メイン・
クロック
val aresetn = IO(Input(Bool()))

val rmii_clock = IO(Input(Clock())) // RMIIのクロック
val rmii_reset = IO(Input(Bool())) // RMIIのリセット

val in_tdata = IO(Input(UInt(8.W))) // MACからの受信データのストリーム入力
val in_tvalid = IO(Input(Bool()))
val in_tready = IO(Output(Bool()))
val in_tlast = IO(Input(Bool()))

val out_tdata = IO(Output(UInt(8.W))) // MACへの送信データのストリーム出力
val out_tvalid = IO(Output(Bool()))
val out_tready = IO(Input(Bool()))
val out_tlast = IO(Output(Bool()))

val gpio_in = IO(Input(UInt(8.W))) // UDP GPIOへのスイッチ信号入力
val gpio_out = IO(Output(UInt(72.W))) // UDP GPIOからの出力

val out_ws = IO(Output(Bool())) // DACのWS出力
val out_bclk = IO(Output(Bool())) // DACのBCLK出力
val out_data = IO(Output(Bool())) // DACのDIN出力

val dbg_buffering = IO(Output(Bool())) // デバッグ用のバッファリング中信号出力
val dbg_probeOut = IO(Output(Bool())) // デバッグ用の埋め込みロジックアナライザ出力

// RMII/MACのクロックとパケット処理系のメインクロックに載せ替えるための非同期FIFO
val txAsyncFifo = withClockAndReset(rmii_clock, rmii_reset) { Module(new AsyncFIFO(Flushable(UInt(8.W)), 3))}
val rxAsyncFifo = withClockAndReset(rmii_clock, rmii_reset) { Module(new AsyncFIFO(Flushable(UInt(8.W)), 3))}

val txFifo = txAsyncFifo
val rxFifo = rxAsyncFifo
txFifo.io.readClock := rmii_clock // 送信FIFOの読み出しはRMIIクロック
txFifo.io.readReset := rmii_reset
txFifo.io.writeClock := clock // 送信FIFOの書き込みはメインクロック
txFifo.io.writeReset := !aresetn

txFifo.io.read.valid <> out_tvalid
txFifo.io.read.ready <> out_tready
txFifo.io.read.bits.data <> out_tdata
txFifo.io.read.bits.last <> out_tlast

rxFifo.io.readClock := clock // 受信FIFOの読み出しはメインクロック
rxFifo.io.readReset := !aresetn
rxFifo.io.writeClock := rmii_clock // 受信FIFOの書き込みはRMIIクロック
rxFifo.io.writeReset := rmii_reset

rxFifo.io.write.valid <> in_tvalid
rxFifo.io.write.ready <> in_tready
rxFifo.io.write.bits.data <> in_tdata
rxFifo.io.write.bits.last <> in_tlast

withClockAndReset(clock, !aresetn) { // このブロック内はclockに同期,!aresetnでリセット
val service = Module(new EthernetService)
// 受信Queue (2048バイト) - 1パケット分は収められる長さ
val rxQueue = Module(new Queue(Flushable(UInt(8.W)), 2048))
rxQueue.io.deq.valid <> service.io.in.valid
rxQueue.io.deq.ready <> service.io.in.ready
rxQueue.io.deq.bits.data <> service.io.in.bits.data
rxQueue.io.deq.bits.last <> service.io.in.bits.last
service.io.in.bits.keep := 1.U
// 送信パケット・キュー (lastがアサートされるまで出力を開始しないキュー)
val txPacketQueue = Module(new PacketQueue(Flushable(UInt(8.W)), 2048))
txPacketQueue.io.write.valid <> service.io.out.valid
txPacketQueue.io.write.ready <> service.io.out.ready
txPacketQueue.io.write.bits.data <> service.io.out.bits.data
txPacketQueue.io.write.bits.last <> service.io.out.bits.last

rxQueue.io.enq.valid <> rxAsyncFifo.io.read.valid
rxQueue.io.enq.ready <> rxAsyncFifo.io.read.ready
rxQueue.io.enq.bits.data <> rxAsyncFifo.io.read.bits.data
rxQueue.io.enq.bits.last <> rxAsyncFifo.io.read.bits.last

txPacketQueue.io.read.valid <> txAsyncFifo.io.write.valid
txPacketQueue.io.read.ready <> txAsyncFifo.io.write.ready
txPacketQueue.io.read.bits.data <> txAsyncFifo.io.write.bits.data
txPacketQueue.io.read.bits.last <> txAsyncFifo.io.write.bits.last
// チャネル数 = 2
val audioChannels = 2
// UDPサービス・マルチプレクサの定義
val serviceMux = Module(new UdpServiceMux(1, Seq(
(context => context.destinationPort === 10000.U), // 10000~10003のUDPポートのサービスを定義
(context => context.destinationPort === 10001.U), //
(context => context.destinationPort === 10002.U), //
(context => context.destinationPort === 10003.U), // /
)))
service.io.port <> serviceMux.io.in

// サービス0: 10000番: UDPループバック
val udpLoopback = Module(new UdpLoopback)
serviceMux.io.servicePorts(0) <> udpLoopback.io.port
// サービス1: 10001番: GPIO
val udpGpio = Module(new UdpGpio(numOutputBits = 72)) // GPIO出力72ビット
serviceMux.io.servicePorts(1) <> udpGpio.io.port
gpio_out := udpGpio.io.gpioOut
udpGpio.io.gpioIn := gpio_in
val volumeControl = udpGpio.io.gpioOut(71, 8) // [71:8] のビットを音量制御用に使う

val dbg_bufferCount = WireDefault(0.U(32.W))

// サンプリング・レート: 48kHz
val sampleRate = 48000
val master = Module(new I2sMaster(16, (mainClockFrequencyHz / sampleRate / 2).toInt, 0)) // DAC信号出力モジュール
val audioMixer = Module(new AudioMixerXls(16, audioChannels, 0)) // 音声ミキサー・モジュール
val audioSampler = Module(new AudioSampler(16, audioChannels, 0, (mainClockFrequencyHz / sampleRate).toInt)) // 音声サンプリング・モジュール
for(channelIndex <- 0 until audioMixer.channels) {
val audioBufferSize = 2048 // オーディオ・バッファ: 2048エントリ
val backPressureThreshold = audioBufferSize * 3 / 4 // バッファ空き状況通知しきい値: 3/4
val udpStream = Module(new UdpStreamWriter(backPressureMaxBufferSize = Some(audioBufferSize)))
serviceMux.io.servicePorts(2 + channelIndex) <> udpStream.io.port // UDPサービス (2 + チャネル番号) 番目に接続

// データを16*2ビットの幅に変換する
val widthConverter = Module(WidthConverter(8, 32))
widthConverter.io.enq.valid <> udpStream.io.dataReceived.valid
widthConverter.io.enq.ready <> udpStream.io.dataReceived.ready
widthConverter.io.enq.bits.data <> udpStream.io.dataReceived.bits.data
widthConverter.io.enq.bits.last <> udpStream.io.dataReceived.bits.last
val widthConverterDeq = Wire(Decoupled(UInt(32.W)))
widthConverterDeq.valid <> widthConverter.io.deq.valid
widthConverterDeq.ready <> widthConverter.io.deq.ready
widthConverterDeq.bits <> widthConverter.io.deq.bits.data

// オーディオ・バッファを接続する
val audioBuffer = Module(new AudioBuffer(32, audioBufferSize, audioBufferSize))
audioBuffer.io.dataIn <> widthConverterDeq
// オーディオ・バッファをサンプラーに接続する
audioSampler.io.dataIn(channelIndex) <> audioBuffer.io.dataOut
// オーディオ・バッファ内にバッファ通知しきい値以上のデータがあるかどうか
val audioBufferFilled = audioBuffer.io.bufferedEntries >= backPressureThreshold.U
val audioBufferFilledReg = RegNext(audioBufferFilled, false.B)
val backPressure = udpStream.io.backPressure.get
// オーディオ・バッファのデータがしきい値を下回ったときに通知を送信する
backPressure.valid := audioBufferFilledReg && !audioBufferFilled
backPressure.bits := audioBuffer.io.bufferedEntries

if( channelIndex == 0 ) {
// チャネル0: そのままミキサに入力
audioMixer.io.dataIn(channelIndex) <> audioSampler.io.dataOut(channelIndex)
} else {
// チャネル1: 移動平均フィルタ経由で入力
val filter = Module(new AudioMovingAverageFilter(16, 8))
filter.io.dataIn <> audioSampler.io.dataOut(channelIndex)
audioMixer.io.dataIn(channelIndex) <> filter.io.dataOut
}
// UDP GPIOからのボリューム信号をミキサに接続
audioMixer.io.volumeIn(channelIndex).bits := volumeControl(32*(channelIndex + 1)-1, 32*channelIndex)
audioMixer.io.volumeIn(channelIndex).valid := true.B

if( channelIndex == 0 ) {
// チャネル0のバッファリング信号とバッファ内エントリ数をデバッグ用信号に出力
dbg_buffering := audioBuffer.io.buffering
dbg_bufferCount := audioBuffer.io.bufferedEntries
}
}

// オーディオ・ミキサをDAC出力モジュールに接続
master.io.dataIn.valid <> audioMixer.io.dataOut.valid
master.io.dataIn.ready <> audioMixer.io.dataOut.ready
// 音声出力レベル調整用: 2^attenuation 分の1 にレベルを下げる
val attenuation = 0
if( attenuation > 0 ) {
val lch = (audioMixer.io.dataOut.bits(15, 0) >> attenuation)
val rch = (audioMixer.io.dataOut.bits(31, 16) >> attenuation)
master.io.dataIn.bits := Cat(Fill(attenuation, rch(15-attenuation)), rch, Fill(attenuation, lch(15-attenuation)), lch)
} else {
master.io.dataIn.bits := audioMixer.io.dataOut.bits
}

master.io.clockEnable := true.B
out_bclk := clock.asBool
out_data := master.io.dataOut
out_ws := master.io.wordSelect

// デバッグ用の埋め込みロジックアナライザを構成
val probe = Module(new diag.Probe(new diag.ProbeConfig(bufferDepth = 512, triggerPosition = 512 - 16), 33))
probe.io.in := Cat(dbg_buffering, dbg_bufferCount)
probe.io.trigger := dbg_buffering
val probeFrameAdapter = Module(new diag.ProbeFrameAdapter(probe.width))
probeFrameAdapter.io.in <> probe.io.out
val probeUartTx = Module(new UartTx(numberOfBits = 8, baudDivider = (mainClockFrequencyHz / BigInt(115200)).toInt))
probeUartTx.io.in <> probeFrameAdapter.io.out
dbg_probeOut := probeUartTx.io.tx
}
}