RISC-Vの実装であるRocket-Chipは、RISC-Vの最新使用に追従しているため最初に見るべきデザインとしてはよくできているが、
- Chiselで記述されており(初心者には)可読性が低い。
- 外部のSoCプラットフォームについて情報がない
ことから、なかなかオリジナルのSoCをくみ上げるためのデザインとしてポーティングしにくい。
とはいえ、TileLinkは単なるバスのはずだし、クロックとリセットさえ突っ込めばとりあえずフェッチリクエストが上がってくるはずだ。 そのあたりを解析して、Rocket-Chipを利用したオリジナルSoCプラットフォーム構築のための情報収集を行っていこう。
関連記事
- SiFiveのFreedom E300 Platformを単体でカスタマイズできるようにする (1. 全体構造からブートROMへのリクエストまでの仕組み)
- SiFiveのFreedom E300 Platformを単体でカスタマイズできるようにする (2. バスのマップと新しいモジュールの追加)
- SiFiveのFreedom E300 Platformを単体でカスタマイズできるようにする (3. SRAMモジュールを作成して接続、テストパタンで動作を確認する)
- SiFiveのFreedom E300 Platformを単体でカスタマイズできるようにする (4. TileLinkの解析とコンフィグレーション設定)
Freedmo-SoC に独自のRAMモジュールを配置する
Rocket-Chipには、P-Busに接続できるモジュールとしてはマスクROMやPLICなどが定義されている。 この中にTestRAMというモジュールがあるが、これはシミュレーション用で、論理合成はできない仕組みだ。
rocket-chip/src/main/scala/devices/tilelink$ tree . ├── BootROM.scala ├── BusBlocker.scala ├── BusBypass.scala ├── Clint.scala ├── Error.scala ├── MaskROM.scala ├── Plic.scala ├── TestRAM.scala └── Zero.scala
// Do not use this for synthesis! Only for simulation. class TLTestRAM(address: AddressSet, executable: Boolean = true, beatBytes: Int = 4, errors: Seq[AddressSet] = Nil)(implicit p: Parameters) extends LazyModule
そこで、SimpleなRAMを作成して接続してみる。
class TLSimpleRAM(c: SimpleRAMParams)(implicit p: Parameters) extends LazyModule { val beatBytes = c.width/8 val node = TLManagerNode(Seq(TLManagerPortParameters( Seq(TLManagerParameters( address = AddressSet.misaligned(c.address, c.depth*beatBytes), resources = new SimpleDevice("ram", Seq("msyksphinz,simpleRAM")).reg("mem"), ... lazy val module = new LazyModuleImp(this) { val memory = Mem(1024, UInt(width = c.width)) val (in, edge) = node.in(0) val mem_address = edge.addr_hi(in.a.bits.address - UInt(c.address))(log2Ceil(c.depth)-1, 0) val read = (in.a.bits.opcode === TLMessages.Get) when (in.a.fire() && !read) { memory(mem_address) := in.a.bits.data // memory(mem_address) := 0.U } ...
これをP-Busのマップ上に定義して、FIRRTLでVerilogファイルを生成する。 - freedom/src/main/scala/everywhere/e300artydevkit/Config.scala
// Freedom E300 Arty Dev Kit Peripherals class E300DevKitPeripherals extends Config((site, here, up) => { case PeripheryGPIOKey => List( GPIOParams(address = 0x10012000, width = 32, includeIOF = true)) case PeripheryPWMKey => List( PWMParams(address = 0x10015000, cmpWidth = 8), PWMParams(address = 0x10025000, cmpWidth = 16), PWMParams(address = 0x10035000, cmpWidth = 16)) case PeripherySPIKey => List( SPIParams(csWidth = 4, rAddress = 0x10024000, sampleDelay = 3), SPIParams(csWidth = 1, rAddress = 0x10034000, sampleDelay = 3)) // case PeripherySPIFlashKey => List( // SPIFlashParams( // fAddress = 0x20000000, // rAddress = 0x10014000, // sampleDelay = 3)) case PeripheryUARTKey => List( UARTParams(address = 0x10013000), UARTParams(address = 0x10023000)) case PeripheryI2CKey => List( I2CParams(address = 0x10016000)) case PeripheryMockAONKey => MockAONParams(address = 0x10000000) case PeripheryMaskROMKey => List( MaskROMParams(address = 0x10000, name = "BootROM") ) case PeripherySimpleRAMKey => List( SimpleRAMParams(address = 0x20000000, name = "TestRAM") ) })
以下のように入力して、Verilogファイルを生成する。
make -f Makefile.e300artydevkit clean && make -f Makefile.e300artydevkit verilog && make -f Makefile.e300artydevkit romgen
VerilogシミュレーションでSimpleRAMへのアクセスを確認する
次に、アセンブリコードを書いてSimpleSRAMモジュールへアクセスが入ることを確認しよう。 このCoreplex E31のメモリマップでは、Uncached / Cachedをどう使い分ければよいのかわからないが、デフォルトでは必ずMissになるはずなのでReadは上手くできるはずだ。
.section .text.init .option norvc .globl _start _start: csrr a0, mhartid li a0, 0x20000000 li a1, 0xdeadbeef lw t0, 0x00(a0) lw t1, 0x04(a0) lw t2, 0x08(a0) lw t3, 0x0c(a0) lw t4, 0x10(a0) lw t5, 0x14(a0) lw t6, 0x18(a0) lw a1, 0x1c(a0) la a1, dtb li t0, XIP_TARGET_ADDR jr t0 .section .rodata dtb: .incbin DEVICE_TREE
波形を確認すると、以下のようにリクエストがSimpleSRAMに到達しているのが確認できた。
しかし、Store命令を発行しても、SimpleSRAMモジュールにリクエストが到達しない。考えられるのは、 - キャッシュに入ったままFlushされない - そもそも発行する命令とアドレスが間違っている
だが、どちらも納得できない。DCacheにリクエストが入った後TLBへの参照が入っているようだが、その辺りでコケていないだろうか?慎重にデバッグする必要がありそうだ。