Running VexRiscv SoC with AXI4 crossbar on TinyFPGA BX.
前回まで、VexRiscv プロジェクトで公開されている Murax という SoC で遊んで来ました。Murax は非常に小型の SoC 実装例(デモ)となっていて、小さな論理規模で実装できるのがポイントです。しかしながら、私は今後、この SoC に CPU 以外のバスマスタ(DMA)を追加する実験をしたいと思っており、この Murax が持っているバス MuraxMasterArbiter では、追加のバスマスタを追加するのは難しそうです。
AXI4 クロスバ付き SoC「Briey」
そこで、今回より新たに Briey というもう一つ別の SoC 実装例をベースに評価を進めていきたいと思います。Briey のブロック図を引用させて頂きます。(引用元: https://github.com/SpinalHDL/VexRiscv#briey-soc)
Briey は、Axi4CrossbarFactory というライブラリを使って AXI4 のクロスバスイッチを実装しているのが特徴です。このスイッチを使うと複数のバスマスタを接続できそうです。(詳細は勉強中)
TinyFPGA BX で動かす
そこで、VexRiscv のデモに含まれる Briey を TinyFPGA BX で動くようにしてみました。GitHub リポジトリはこちらです。
バス構成だけ Briey を踏襲し、
- CPU 構成は Murax と同等にする(論理サイズを小さく保つため)
- ペリフェラルから SdramCtrl、gpioB、VgaCtrl を取り除く
ようにしました。
いくつか注意点と修正点
今回の作業でいくつか落とし穴にはまり苦労しましたので、備忘録を兼ねてまとめておきます。
シフト演算命令が動作しなくなる
Murax の cpuPlugins 設定を注意深くコピーしていったのですが、一つだけ、LightShifterPlugin を追加し忘れるというポカをしました。これを忘れると、シフト命令(SLL や SLLI など)が正しく動作しなくなります。
本来であれば、LightShifterPlugin か FullBarrelShifterPlugin を忘れていたら警告を出して欲しいところですが、おそらく、DecoderSimplePlugin に catchIllegalInstruction = true を入れていれば、こういう問題は避けれらる、ということでしょう。
UART が動かなくなる
実は最初、私は UART が動いていないのではなく、SpiSlave の割込が動いていないのではないかと(間違って)推測してしまいました。人間、何か先入観があると本来の問題に気づかなくなるものです。
デバッグしていたとき、ふと、FPGA 側 UART の RxD に FTDI チップの TxD を繋いでみると、なんと「SpiSlave の割込が届いているように見える」ではないですか。最初に分かったのは、FPGA 側の RxD をプルアップしてあげると、問題が消えるのです。確かに、RxD をプルアップしないと入力がずっと low になってしまうのですが、なぜ UART の RxD が「SpiSlave の割込に影響を与えるんだ!?」と、まる一日悩んでいました。
実は、、、SpiSlave に影響を与えているのではなくて、RxD が low のままだと、SpinalHDL の UART ライブラリが、RS-232 で使われる break 信号を検出してしまい、UART の機能が「送信も含めて」停止してしまう、というのが本当のところだったのです。ギャフンです。SpinalHDL の UART コードを読んでいて、ようやく気づいた次第です。
という訳で、2つの対策を入れることにしました。一つはもちろん、RxD ピンを内部プルアップにする、というものです。RxD が low だと break というのは理解できますが、RxD を結線しない使い方だってあるからです。私がでっち上げた Briey_iCE40_tinyfpga_bx クラスでは、以下のようなコードを追加しています。
import spinal.lib.blackbox.lattice.ice40.SB_IO
object Briey_iCE40_tinyfpga_bx{
// 略
case class Briey_iCE40_tinyfpga_bx(nClkDiv: Int = 0) extends Component{
val io = new Bundle {
// 略
val uart_rxd = in(Analog(Bool)) // ← ここに注意
}
// 略
class SB_IO_PULLUP(pinType: String) extends SB_IO(pinType) {
addGeneric("PULLUP", 1)
}
val rxdPullup = new SB_IO_PULLUP(pinType = "000001")
rxdPullup.PACKAGE_PIN := io.uart_rxd
briey.io.uart.rxd := rxdPullup.D_IN_0
// 略
}
}
もう一つの対策は、C で書かれたデモコードのほうです。SpinalHDL の UART では、レジスタマップに break 信号の検出を読み出せるビットがあるので、以下のようにして、LED 点滅を早めて警告してみるようにしました。
typedef struct
{
volatile uint32_t DATA;
volatile uint32_t STATUS;
volatile uint32_t CLOCK_DIVIDER;
volatile uint32_t FRAME_CONFIG;
volatile uint32_t ERROR; // ← 追加
} Uart_Reg;
void main()
{
// 略
while (1) {
//略
// Check UART Rx break
if (UART->ERROR & (1 << 8))
GPIO_A->OUTPUT ^= 0x80; // Toggle led 7
}
}
これでようやく、AXI4 クロスバスイッチの勉強に移れそうです、、、が、クロスバスイッチの実装は難しそうです。どうなることやら。
今日はここまで。