FPGA開発日記

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

LiteXによるSoC環境構築を試行する (4. PLICのデバッグ)

https://raw.githubusercontent.com/enjoy-digital/litex/master/doc/litex.png

久しぶりに自作CPUのコアをLiteXに接続して、シミュレーション作業をしている。 FPGAの環境はどうにかなりつつあるので、シミュレーションでBIOSが動作するのを確認すれば、まずはなんとかなるんじゃないかなと思い作業中。

最終的にはキーボードからの入力を受け付けてコマンドを打てたりしてほしいのだが、まだそこまで言っていない。

以下のコードの解析の続き。

 void uart_isr(void)
 {
     unsigned int stat, rx_produce_next;

     stat = uart_ev_pending_read();

     if(stat & UART_EV_RX) {
         while(!uart_rxempty_read()) {
             rx_produce_next = (rx_produce + 1) & UART_RINGBUFFER_MASK_RX;
             if(rx_produce_next != rx_consume) {
                 rx_buf[rx_produce] = uart_rxtx_read();
                 rx_produce = rx_produce_next;
             }
             uart_ev_pending_write(UART_EV_RX);
             #if defined(__cva6__)
                 asm volatile("fence\n");
             #endif
         }
     }

     if(stat & UART_EV_TX) {
         uart_ev_pending_write(UART_EV_TX);
         while((tx_consume != tx_produce) && !uart_txfull_read()) {
             uart_rxtx_write(tx_buf[tx_consume]);
             tx_consume = (tx_consume + 1) & UART_RINGBUFFER_MASK_TX;
         }
     }
 }

まず、uart_ev_pending_read()CSR_BASEと呼ばれるメモリ領域にアクセスがある。 まあこれはおそらく単なるメモリ領域で、とりあえず初期値が読めていればよい。

static inline uint32_t uart_ev_pending_read(void) {
    return csr_read_simple((CSR_BASE + 0x1810L));
}

rx_producerx_consumeは実際に入力データを授受するためのバッファで、これを読むためには、

  1. uart_rxempty_read()の確認
  2. 実際にバッファを読む

というステップで進む。まず、uart_rxempty_read()はこれも単純に無視すればよく、まあ初期値がゼロならば簡単にパスしてくれるので普通に条件は成立する。 uart_rxtx_read()は以下のように定義されている。上記のメモリマップと一緒で、こんなところにメモリ領域が宣言されているの?という疑問があるが、これはもうちょっとよく見てみないといけない。

static inline uint32_t uart_rxempty_read(void) {
    return csr_read_simple((CSR_BASE + 0x1808L));
}

最後に、CLAIMレジスタをクリアしなければならない。ここがポイントで、これをちゃんと実装しないとclaimがかかり続けて無限ループになる。

 void isr(void)
 {
     unsigned int claim;

     // ① 次に戻ってきたときに、ずっとこれが成立し続けて無限ループになる。
     while ((claim = *((unsigned int *)PLIC_CLAIM))) {
         switch (claim - PLIC_EXT_IRQ_BASE) {
         case UART_INTERRUPT:
             uart_isr();
             break;
         default:
             printf("## PLIC: Unhandled claim: %d\n", claim);
             printf("# plic_enabled:    %08x\n", irq_getmask());
             printf("# plic_pending:    %08x\n", irq_pending());
             printf("# mepc:    %016lx\n", csrr(mepc));
             printf("# mcause:  %016lx\n", csrr(mcause));
             printf("# mtval:   %016lx\n", csrr(mtval));
             printf("# mie:     %016lx\n", csrr(mie));
             printf("# mip:     %016lx\n", csrr(mip));
             printf("###########################\n\n");
             break;
         }
         // ② これをちゃんとしないと無限ループになる。CLAIMレジスタは、当該割り込み番号を書き込むことでその割り込みをクリアする。
         *((unsigned int *)PLIC_CLAIM) = claim;
     }
 }