FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

FPGA開発日記 カテゴリ別インデックス

RISC-VにおけるRVWMOの仕様について読み直す

続きを読む

LiteXのBIOSソフトウェアを独自に構築する方法調査 (6. バスをAXIに変える)

前回の続き、自作CPUがFPGA上で動作したので一安心。 とりあえず次の試行として、LiteXのSoC周りがWishboneをベースに作ってあるので、AXIに変えてみる。 AXIに変えるためには、とりあえずコマンドラインで以下のようにオプションを変えればいいらしい。

python3 -m litex_boards.targets.digilent_nexys_video --cpu-type=mycpu --build --output-dir mycpu_fpga_axi --sys-clk-freq 30e6 \
     --bus-standard axi --bus-data-width 64

とりあえず、SRAMのビット幅がデフォルトの32から倍の64ビットになっている。

reg           main_basesoc_mmio_a2w_b_valid = 1'd0;
reg   [127:0] main_basesoc_mmio_a2w_r_payload_data = 128'd0;
reg     [1:0] main_basesoc_mmio_a2w_r_payload_resp = 2'd0;
reg           main_basesoc_mmio_a2w_r_ready = 1'd0;
reg           main_basesoc_mmio_a2w_r_valid = 1'd0;
reg   [127:0] main_basesoc_mmio_a2w_w_payload_data = 128'd0;
reg    [15:0] main_basesoc_mmio_a2w_w_payload_strb = 16'd0;
reg           main_basesoc_mmio_a2w_w_ready = 1'd0;
reg           main_basesoc_mmio_a2w_w_valid = 1'd0;
reg           main_basesoc_mmio_axi_ar_first = 1'd0;
wire          main_basesoc_mmio_axi_ar_last;
reg           main_basesoc_mmio_axi_ar_param_dest = 1'd0;

ただし、変換のパスについては、アウトスタンディング数があまり大きくないのか?性能の面では少し疑問が残るね。 もうちょっと内部を詳細に見ていく必要がありそう。

LiteXのBIOSソフトウェアを独自に構築する方法調査 (5. FPGAでの動作確認)

前回の続き。結局LiteXのFPGA環境を丸ごとVerilatorにもってきて波形を取った。 やはり波形のデバッグは楽だ。実行が停止してしまうのもちゃんと再現できた。

msyksphinz.hatenablog.com

小さなバグだったが、やはり本番FPGA環境をちゃんとシミュレーションできるのは楽だ。 ストールの要因も波形ですぐに特定できた。

修正後のRTLシミュレーションでの正常動作確認、FPGAでの正常動作も確認した。 自作Out-of-Order CPUが、LiteXのプラットフォームでブートしたぞ!

シリアル出力、キーボード入力も問題ない。

litex> ident
Ident: LiteX SoC on Nexys Video 2024-05-02 14:12:00
litex> 

とりあえず正常動作まで確認したので、あとはクリティカルパスの改善と、ベンチマークの動作確認だな。 あとLinuxとかも検討しなきゃ。

LiteXのBIOSソフトウェアを独自に構築する方法調査 (4. FPGAでの動作確認)

msyksphinz.hatenablog.com

前回の結果に基づいて、FPGAで再度動作させてみた。 結果としてはRTLシミュレーションと全く同じ状態まで来たぞ。ただしやっぱりここで止まってしまう。 SDRAMの初期化に問題があるのか?

        __   _ __      _  __
       / /  (_) /____ | |/_/
      / /__/ / __/ -_)>  <
     /____/_/\__/\__/_/|_|
   Build your hardware, easily!

 (c) Copyright 2012-2024 Enjoy-Digital
 (c) Copyright 2007-2015 M-Labs

 BIOS built on Apr 29 2024 00:47:29
 BIOS CRC passed (f2e8460c)

 LiteX git sha1: 573e23072

--=============== SoC ==================--
CPU:            Scariv @ 30MHz
BUS:            wishbone 32-bit @ 4GiB
CSR:            32-bit data
ROM:            128.0KiB
SRAM:           8.0KiB
L2:             8.0KiB
SDRAM:          512.0MiB 16-bit @ 240MT/s (CL-7 CWL-5)
MAIN-RAM:       512.0MiB

--========== Initialization ============--
Initializing SDRAM @0x40000000...
Switching SDRAM to software control.
Read leveling:
  m0, b00: |000000000000000

シミュレーション用の環境はなんか違う構成で動いているので、ちょっとまだ良く分からないなあ...

litex_sim --cpu-type=mycpu --output-dir mycpu_sim
        __   _ __      _  __
       / /  (_) /____ | |/_/
      / /__/ / __/ -_)>  <
     /____/_/\__/\__/_/|_|
   Build your hardware, easily!

 (c) Copyright 2012-2024 Enjoy-Digital
 (c) Copyright 2007-2015 M-Labs

 BIOS built on May  2 2024 08:58:31
 BIOS CRC passed (45c5807a)

 LiteX git sha1: 573e23072

--=============== SoC ==================--
CPU:            MyCPU @ 1MHz
BUS:            wishbone 32-bit @ 4GiB
CSR:            32-bit data
ROM:            128.0KiB
SRAM:           8.0KiB


--============== Boot ==================--
Booting from serial...
Press Q or ESC to abort boot completely.
sL5DdSMmkekro
Timeout
No boot medium found

--============= Console ================--

litex>

LiteXのBIOSソフトウェアを独自に構築する方法調査 (3. シミュレーション環境の確認)

msyksphinz.hatenablog.com

FPGAでの自作CPUの挙動がどうしてもデバッグできなくなってきたので、FPGA向けRTLシミュレーションの環境を自分で構築することにした。

問題は、LiteXのVerilatorシミュレーション環境と、FPGA向けの論理合成環境は、生成されるRTLが異なる点だと思う。 たとえば、シリアル通信のためのデバイスの制御や、割り込み、シリアル通信FIFOのオーバーフロー制御などの確認が足りない。

rm -rf obj_dir/
make -C . -f Makefile \
     CC_SRCS="--cc ${LITEX_WORK}litex-work/pythondata-cpu-scariv/pythondata_cpu_scariv/system_verilog/tb/sim_pkg.sv \
     --cc ${LITEX_WORK}litex-work/pythondata-cpu-scariv/pythondata_cpu_scariv/system_verilog/src/litex_defines.sv \
     --cc ${LITEX_WORK}litex-work/pythondata-cpu-scariv/pythondata_cpu_scariv/system_verilog/src/riscv_common_pkg.sv \
     --cc ${LITEX_WORK}litex-work/pythondata-cpu-scariv/pythondata_cpu_scariv/system_verilog/src/riscv_fpu_imafdc_pkg.sv \
     --cc ${LITEX_WORK}litex-work/pythondata-cpu-scariv/pythondata_cpu_scariv/system_verilog/src/riscv64_pkg.sv \
     --cc ${LITEX_WORK}litex-work/pythondata-cpu-scariv/pythondata_cpu_scariv/system_verilog/configs/scariv_boomv3_conf_pkg.sv \
/* ... 途中省略 ... */
     --cc ${LITEX_WORK}litex-work/pythondata-cpu-scariv/pythondata_cpu_scariv/system_verilog/src/scariv_bootrom.sv \
     --cc ${LITEX_WORK}litex-work/pythondata-cpu-scariv/pythondata_cpu_scariv/system_verilog/src/distributed_ram.sv \
     --cc ${LITEX_WORK}litex-work/pythondata-cpu-scariv/pythondata_cpu_scariv/system_verilog/src/distributed_mp_ram.sv \
     --cc ${LITEX_WORK}litex-work/pythondata-cpu-scariv/pythondata_cpu_scariv/system_verilog/src/distributed_1rd_ram.sv" \
     OPT_LEVEL=O3 \
     TRACE_FST=1

さらに、テストベンチの環境を作り上げてみる。

  • tb.v
module tb;

logic clk;
logic cpu_reset;

digilent_nexys_video
u_dut
  (
   .clk100    (clk),
   .cpu_reset (cpu_reset),
   .ddram_a       (),
/* ... 途中省略 ... */
   .user_led6(),
   .user_led7(),

   .vadj0()
   );


initial begin
  clk       = 1'b0;
  cpu_reset = 1'b1;
  $display("Simulation Start");
  #1000;
  cpu_reset = 1'b0;
  $display("Simulation Reset Release");
  #100000000;
  $display("Simulation Finish");
  $finish;
end

initial begin
  $dumpfile("dump.fst");
  $dumpvars(100, tb);
  $dumpon;
end

initial begin
  forever begin
    clk = 1'b0;
    #1;
    clk = 1'b1;
    #1;
  end
end

always_ff @ (posedge clk) begin
  if (u_dut.main_basesoc_uart_tx_fifo_rdport_re) begin
    $write("%c", u_dut.storage[u_dut.main_basesoc_uart_tx_fifo_rdport_adr]);
    $fflush;
  end
end

すると、だんだん何が起きているのかわかってきた:

  • 割り込み挿入時のジャンプアドレスがおかしい
  • 割り込み挿入時のどの命令を殺すかの選択がおかしい

この辺を修正すると、RTLシミュレーションでもLiteXのBIOSが起動するようになってきた。

        __   _ __      _  __
       / /  (_) /____ | |/_/
      / /__/ / __/ -_)>  <
     /____/_/\__/\__/_/|_|
   Build your hardware, easily!

 (c) Copyright 2012-2024 Enjoy-Digital
 (c) Copyright 2007-2015 M-Labs

 BIOS built on Apr 29 2024 00:47:29
 BIOS CRC passed (f2e8460c)

 LiteX git sha1: 573e23072

--=============== SoC ==================--
CPU:            Scariv @ 30MHz
BUS:            wishbone 32-bit @ 4GiB
CSR:            32-bit data
ROM:            128.0KiB
SRAM:           8.0KiB
L2:             8.0KiB
SDRAM:          512.0MiB 16-bit @ 240MT/s (CL-7 CWL-5)
MAIN-RAM:       512.0MiB

--========== Initialization ============--
Initializing SDRAM @0x40000000...
Switching SDRAM to software control.
Read leveling:
  m0, b00: |000000000000000

もうちょっと、デバッグだな。

LiteXのUARTハードウェアの確認 (Migenのソースコードを読む)

LiteXのUARTデバイスの挙動を確認したくて、Migenのソースコードをチェックしている:

UARTのデバイスは、Wishboneを経由して接続されている。

  • litex/litex/soc/cores/uart.py
class UARTWishboneBridge(UARTBone):
    def __init__(self, pads, clk_freq, baudrate=115200, cd="sys"):
        self.phy = RS232PHY(pads, clk_freq, baudrate)
        UARTBone.__init__(self, self.phy, clk_freq, cd)

RS232の送信用のハードウェアと、受信用のハードウェアが接続されている。

class RS232PHY(LiteXModule):
    def __init__(self, pads, clk_freq, baudrate=115200, with_dynamic_baudrate=False):
        tuning_word = int((baudrate/clk_freq)*2**32)
        if with_dynamic_baudrate:
            self._tuning_word  = CSRStorage(32, reset=tuning_word)
            tuning_word = self._tuning_word.storage
        self.tx = RS232PHYTX(pads, tuning_word)
        self.rx = RS232PHYRX(pads, tuning_word)
        self.sink, self.source = self.tx.sink, self.rx.source

TXのほうを確認すると、ステートマシンが作成してある。

        # FSM                                                                                                                                                                                                                                                                                                                                                                             
        self.fsm = fsm = FSM(reset_state="IDLE")
        fsm.act("IDLE",
            # Reset Count and set TX to Idle.                                                                                                                                                                                                                                                                                                                                             
            NextValue(count,   0),
            NextValue(pads.tx, RS232_IDLE),
            # Wait for TX data to transmit.                                                                                                                                                                                                                                                                                                                                               
            If(sink.valid,
                NextValue(pads.tx, RS232_START),
                NextValue(data, sink.data),
                NextState("RUN")
            )
        )
        fsm.act("RUN",
            # Enable Clock Phase Accumulator.                                                                                                                                                                                                                                                                                                                                             
            clk_phase_accum.enable.eq(1),
            # On Clock Phase Accumulator tick:                                                                                                                                                                                                                                                                                                                                            
            If(clk_phase_accum.tick,
                # Set TX data.                                                                                                                                                                                                                                                                                                                                                            
                NextValue(pads.tx, data),
                # Increment Count.                                                                                                                                                                                                                                                                                                                                                        
                NextValue(count, count + 1),
                # Shift TX data.                                                                                                                                                                                                                                                                                                                                                          
                NextValue(data, Cat(data[1:], RS232_STOP)),
                # When 10-bit have been transmitted...                                                                                                                                                                                                                                                                                                                                    
                If(count == (10 - 1),
                    # Ack sink and return to Idle.                                                                                                                                                                                                                                                                                                                                        
                    sink.ready.eq(1),
                    NextState("IDLE")
                )
            )
        )

2つのステートが存在している:

  • IDLE : 入力を受け付けると、pads.txにSTARTコマンド(=0)を打ち込み、dataレジスタに書き込みデータを格納し、RUNステートに遷移する。
  • RUN : clk_phase_accum信号をベースに駆動する。これはRS232のクロックと動作周波数の比率で動作するカウンタで、RS232の信号制御用のカウントを行う。
    • countによりすべての文字列を出力すれば、そこで動作終了。そうでなければ、次のdataビットを出力する。

と、非常にシンプルなステートマシンを組んでいることが分かった。これをベースに、FPGAでの挙動を確認していこうと思う。 ポイントは、データバッファとステートマシンだな。

自作CPUのSpikeモデルシミュレータ環境の変更 (4. LiteX BIOSのブート)

自作CPUのSpikeシミュレータをかなり久しぶりにアップデートすると、いろんな関数が変わっていてかなり戸惑ってしまった。

LiteXとSpikeの設定を変更できるようにして、とりあえずLiteXモードで動作するようにした。 Spike側のBootROMを削除する必要がある。

diff --git a/riscv/sim.cc b/riscv/sim.cc
index f4919c91..5714549a 100644
--- a/riscv/sim.cc
+++ b/riscv/sim.cc
@@ -69,7 +69,7 @@ sim_t::sim_t(const cfg_t *cfg, bool halted,
   for (auto& x : mems)
     bus.add_device(x.first, x.second);
 
-  bus.add_device(DEBUG_START, &debug_module);
+  // bus.add_device(DEBUG_START, &debug_module);
 
   socketif = NULL;
 #ifdef HAVE_BOOST_ASIO
@@ -376,8 +376,8 @@ void sim_t::set_rom()
   const int align = 0x1000;
   rom.resize((rom.size() + align - 1) / align * align);
 
-  std::shared_ptr<rom_device_t> boot_rom(new rom_device_t(rom));
-  add_device(DEFAULT_RSTVEC, boot_rom);
+  // std::shared_ptr<rom_device_t> boot_rom(new rom_device_t(rom));
+  // add_device(DEFAULT_RSTVEC, boot_rom);
 }

とりあえずこれで動き始めた。今度はmain()に到達したところで落ちる。要解析...

MW4(0x000000000c200000)=>0000000000000000
GPR[00](0) <= 0000000000000000
GPR[10](86) <= 0000000000001000
GPR[10](83) <= 0000000000000880
33526 : L1D Evict       : c200000(00000) : 00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000
33526 : EVict ISS Check : c200000        : 00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000
8385 : RTL(43,1) Exception Cause = CSR Update Flush(27) PC=000000000168, Inst=30451073, csrw    mie, a0
GPR[01](52) <= 0000000000000170
8418 : RTL(12,1) Exception Cause = Illegal Instruction(2) PC=00000000174c, Inst=00000000, c.unimp
==========================================
8418 : Exception Happened(12,1) : Cause = Illegal Instruction(2)
==========================================
==========================================
Wrong PC: RTL = 0000000000000020, ISS = 000000000000174c
==========================================
===============================
SIMULATION FINISH : FAIL (CODE=100)
RUNNING TIME : 8442
===============================