將 nommu Linux (M-mode) 移植到黑金 AX301 上的 NEORV32 RV32IMAC 軟核,使用 32 MB SDRAM,通過 UART 控制台達到 BusyBox shell。
| 組件 | 地址 | 狀態 |
|---|---|---|
| IMEM | 0x00000000 (8 KB) |
✅ 可用 |
| SDRAM | 0x40000000 (32 MB) |
✅ 已驗證 |
| DMEM | 0x80000000 (8 KB) |
✅ 可用 |
| CLINT | 0xFFF40000 |
✅ 已啟用 |
| UART0 | 0xFFF50000 |
✅ 已啟用 |
| GPIO | 0xFFFC0000 |
✅ 已啟用 |
| SPI | 0xFFF80000 |
❌ 未啟用 |
| ICACHE | 64B blocks, non-burst | ✅ 已啟用 |
| DCACHE | — | ❌ 未啟用 |
| U-Boot | SDRAM 0x40000000 |
✅ 已跑通 |
| FPGA LE 使用率 | 4,136 / 6,272 (66%) |
Tip
這一步不需要修改 RTL,只需驗證。
Linux 的 riscv,clint0 驅動期望的 CLINT 記憶體佈局:
BASE + 0x0000 : MSIP[0] (4 bytes, 軟件中斷 pending)
BASE + 0x0004 : MSIP[1] (下一個 hart,不用管)
...
BASE + 0x4000 : MTIMECMP[0] (8 bytes, hart 0 的定時器比較值)
BASE + 0x4008 : MTIMECMP[1] (hart 1, 不用管)
...
BASE + 0xBFF8 : MTIME (8 bytes, 全局定時器)
NEORV32 的 CLINT 結構體 (neorv32_clint.h):
typedef volatile struct {
uint32_t MSWI[4096]; // offset 0x0000, 4096 × 4 = 0x4000 bytes
subwords64_t MTIMECMP[4095]; // offset 0x4000, 4095 × 8 = 0x7FF8 bytes
subwords64_t MTIME; // offset 0x4000 + 0x7FF8 = 0xBFF8
} neorv32_clint_t;佈局計算:
MSWI[0]在BASE + 0x0000→ ✅ 對應 Linux 的MSIP[0]MTIMECMP[0]在BASE + 4096*4 = BASE + 0x4000→ ✅ 完全匹配MTIME在BASE + 0x4000 + 4095*8 = BASE + 0xBFF8→ ✅ 完全匹配
Important
CLINT 佈局與 Linux 的 riscv,clint0 驅動 100% 兼容!不需要任何修改。
你的 CLINT 基址是 0xFFF40000,所以:
- MSIP[0] =
0xFFF40000 - MTIMECMP[0] =
0xFFF44000 - MTIME =
0xFFF4BFF8
驗證方法(在 U-Boot 中測試):
U-Boot> md 0xFFF4BFF8 2 # 讀取 MTIME,應該看到遞增的值
U-Boot> md 0xFFF4BFF8 2 # 再讀一次,值應該更大
當前配置 FIFO 深度為 1(ax301_top.vhd):
IO_UART0_RX_FIFO => 1, -- FIFO 深度 = 2^1 = 2 entries
IO_UART0_TX_FIFO => 1, -- FIFO 深度 = 2^1 = 2 entriesLinux kernel 啟動時會輸出大量文字。浅 FIFO 會導致 UART 成為瓶頸,拖慢整個啟動過程。
建議修改 rtl/ax301_top.vhd:
IO_UART0_RX_FIFO => 4, -- FIFO 深度 = 2^4 = 16 entries
IO_UART0_TX_FIFO => 4, -- FIFO 深度 = 2^4 = 16 entriesNote
每增加 1 級 FIFO,大約消耗 16-32 個 LE。從 2^1 到 2^4 大約多用 100-200 LE。你有 ~2100 LE 剩餘,完全夠。
目前只有 ICACHE,data bus 每次訪問 SDRAM 都是直接走 XBUS → wb_sdram_ctrl → SDRAM。Linux 的 kernel data(棧、頁表結構、kmalloc 分配等)會頻繁讀寫 SDRAM,沒有 DCACHE 會極其慢。
修改 rtl/ax301_top.vhd:
DCACHE_EN => true, -- 啟用 DCACHE
-- DCACHE 預設使用和 ICACHE 相同的配置:
-- CACHE_BLOCK_SIZE => 64 (16 words per line)
-- 直接映射,1K 或 2K cacheWarning
DCACHE 的資源消耗估算:
- DCACHE 需要額外的 M9K blocks 存儲 tag + data
- 你目前 M9K 使用 60%(166,912 / 276,480 bits)
- 一個 1KB 的 DCACHE 大約需要 8,192 bits (1 M9K block) data + ~1K tag
- 如果 M9K 不夠,可能需要縮小 IMEM(從 8KB 改為 4KB)
先嘗試加 DCACHE,看 Quartus 編譯是否通過。如果 M9K 不夠,再考慮取捨。
如果想在後續階段從 SPI Flash 或 SD 卡啟動 Linux(避免每次 xmodem),可以啟用 SPI:
IO_SPI_EN => true,SPI 大約消耗 200-300 LE。目前有 2100+ LE 剩餘,可行。 但初期先不啟用,等 Linux 跑通後再考慮。
[MODIFY] ax301_top.vhd
-- Peripherals: only what we need
IO_GPIO_NUM => 4,
IO_CLINT_EN => true,
IO_UART0_EN => true,
- IO_UART0_RX_FIFO => 1,
- IO_UART0_TX_FIFO => 1,
+ IO_UART0_RX_FIFO => 4, -- 16-entry FIFO for Linux console
+ IO_UART0_TX_FIFO => 4, -- 16-entry FIFO for Linux console
-- Everything else off
IO_SPI_EN => false,
...
ICACHE_EN => true,
CACHE_BLOCK_SIZE => 64,
CACHE_BURSTS_EN => false,
- DCACHE_EN => false
+ DCACHE_EN => true -- D-cache critical for SDRAM Linux performance完成後:重新編譯 bitstream
cd quartus
quartus_sh --flow compile neorv32_demo
quartus_cpf -c -o bitstream_compression=off neorv32_demo.sof ../neorv32_demo_linux.rbf驗證: 重新燒寫後,用 boot_uboot.py 確認 U-Boot 仍然能正常啟動。
sudo apt-get update
sudo apt-get install -y \
git make gcc g++ bison flex libncurses-dev \
bc cpio rsync unzip python3 perl wget \
qemu-system-misc libssl-devgit clone https://github.com/buildroot/buildroot.git
cd buildroot
git checkout 2024.02.x # LTS 分支,穩定# 使用現成的 RISC-V 32 nommu 配置
make qemu_riscv32_nommu_virt_defconfig
# 構建(約 30-60 分鐘)
make -j$(nproc)
# 驗證構建成功
ls -la output/images/
# 應該看到: Image, loader, rootfs.cpio 等
# 啟動 QEMU
output/host/bin/qemu-system-riscv32 \
-M virt -m 128M -nographic \
-kernel output/images/loader \
-device loader,file=output/images/Image,addr=0x80400000成功標誌: 看到 Linux 啟動日誌,最終出現 ~ # BusyBox shell prompt。
Important
必須先在 QEMU 上成功後再進入下一步。 如果 QEMU 上都跑不通,硬體上更不可能。QEMU 階段的學習要點:
- 理解
make menuconfig的 kernel 配置 - 理解 Buildroot 的目錄結構:
output/build/linux-xxx/是 kernel 源碼 - 理解 DT(Device Tree)如何描述硬體
QEMU 跑通後,提取關鍵配置作為後續參考:
# 查看 kernel 的 .config
cat output/build/linux-*/\.config | grep -E "RISCV|MMU|CLINT|NOMMU|M_MODE|UART"
# 查看 QEMU virt 的 DT
# 在 QEMU 中:
cat /proc/device-tree/compatible
cat /sys/firmware/devicetree/base/chosen/bootargsmkdir -p ${PROJECT_DIR}
cd ${PROJECT_DIR}
mkdir -p board/neorv32_ax301/
mkdir -p configs/
mkdir -p linux/
mkdir -p package/neorv32-uart-driver/最終結構:
${PROJECT_DIR}/
├── board/neorv32_ax301/
│ ├── neorv32_ax301.dts ← Device Tree 源碼
│ ├── linux.config ← Kernel 配置片段
│ └── post-build.sh ← 構建後處理腳本
├── configs/
│ └── neorv32_ax301_defconfig ← Buildroot 配置
├── linux/
│ └── linux.config ← Kernel defconfig 覆蓋
└── Config.in ← Buildroot external tree
建立 board/neorv32_ax301/neorv32_ax301.dts:
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
compatible = "neorv32,ax301";
model = "NEORV32 on AX301 (EP4CE6)";
chosen {
bootargs = "earlycon=neorv32,0xfff50000 console=ttyNEO0,115200";
stdout-path = &uart0;
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
timebase-frequency = <50000000>; /* MTIME 以 CPU 時鐘頻率計數 */
cpu0: cpu@0 {
device_type = "cpu";
compatible = "riscv";
riscv,isa = "rv32imac";
riscv,isa-base = "rv32i";
riscv,isa-extensions = "i", "m", "a", "c", "zicsr", "zifencei";
mmu-type = "riscv,none"; /* 無 MMU = nommu */
clock-frequency = <50000000>;
reg = <0>;
cpu0_intc: interrupt-controller {
#interrupt-cells = <1>;
compatible = "riscv,cpu-intc";
interrupt-controller;
};
};
};
memory@40000000 {
device_type = "memory";
reg = <0x40000000 0x02000000>; /* 32 MB SDRAM */
};
/* CLINT — 與 SiFive CLINT 佈局 100% 兼容 */
clint: clint@fff40000 {
compatible = "riscv,clint0";
reg = <0xfff40000 0x10000>; /* 64 KB 空間 */
interrupts-extended = <&cpu0_intc 3>, /* M-mode 軟件中斷 */
<&cpu0_intc 7>; /* M-mode 定時器中斷 */
};
/* UART0 — 使用自定義 NEORV32 UART 驅動 */
uart0: serial@fff50000 {
compatible = "neorv32,uart";
reg = <0xfff50000 0x08>; /* CTRL + DATA = 8 bytes */
clock-frequency = <50000000>;
current-speed = <115200>;
/*
* UART0 使用 FIRQ #2 (cpu_firq[2])
* FIRQ 映射到 mie/mip 的 bit [16+n]
* 在 M-mode 下,外部中斷走 mcause
* 先用 polling 方式,中斷支持在後期添加
*/
};
/* GPIO — 4-bit, LED 控制 */
gpio: gpio@fffc0000 {
compatible = "neorv32,gpio";
reg = <0xfffc0000 0x08>;
/* 初期不需要 GPIO 驅動,僅作為調試用 */
};
};
建立 board/neorv32_ax301/linux.config:
# ── 架構 ──
CONFIG_RISCV=y
CONFIG_ARCH_RV32I=y
CONFIG_RISCV_ISA_C=y
CONFIG_RISCV_ISA_M=y
# ── M-mode Nommu ──
CONFIG_MMU=n
CONFIG_RISCV_M_MODE=y
# ── 記憶體 ──
CONFIG_PAGE_OFFSET=0x40000000
CONFIG_PHYS_RAM_BASE=0x40000000
CONFIG_DEFAULT_MEM_START=0x40000000
CONFIG_DEFAULT_MEM_SIZE=0x02000000
# ── 定時器 ──
CONFIG_CLINT_TIMER=y
# ── 控制台(初期用 earlycon polling) ──
CONFIG_SERIAL_EARLYCON=y
CONFIG_EARLY_PRINTK=y
CONFIG_PRINTK=y
# ── 文件系統 ──
CONFIG_BLK_DEV_INITRD=y
CONFIG_INITRAMFS_SOURCE=""
# Buildroot 會自動設置 initramfs
# ── 精簡配置(關閉不需要的) ──
CONFIG_NET=n
CONFIG_BLOCK=n
CONFIG_MODULES=n
CONFIG_SMP=n
CONFIG_SWAP=n
CONFIG_PROC_FS=y
CONFIG_SYSFS=y
CONFIG_TMPFS=y
# ── 調試 ──
CONFIG_DEBUG_KERNEL=y
CONFIG_DEBUG_INFO=y
CONFIG_PANIC_TIMEOUT=0
CONFIG_STACKTRACE=y
建立 configs/neorv32_ax301_defconfig:
# 架構
BR2_riscv=y
BR2_RISCV_32=y
BR2_riscv_custom=y
BR2_RISCV_ISA_RVM=y
BR2_RISCV_ISA_RVA=y
BR2_RISCV_ISA_RVC=y
BR2_RISCV_ABI_ILP32=y
# 工具鏈
BR2_TOOLCHAIN_BUILDROOT=y
BR2_TOOLCHAIN_BUILDROOT_MUSL=y
# Kernel
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.6"
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL_NEORV32_PATH)/board/neorv32_ax301/linux.config"
BR2_LINUX_KERNEL_DTS_SUPPORT=y
BR2_LINUX_KERNEL_CUSTOM_DTS_PATH="$(BR2_EXTERNAL_NEORV32_PATH)/board/neorv32_ax301/neorv32_ax301.dts"
# Root filesystem
BR2_TARGET_ROOTFS_CPIO=y
BR2_TARGET_ROOTFS_INITRAMFS=y
# BusyBox (默認配置即可)
BR2_PACKAGE_BUSYBOX=y
# 其他
BR2_TARGET_GENERIC_HOSTNAME="neorv32"
BR2_TARGET_GENERIC_ISSUE="Welcome to NEORV32 Linux"
BR2_SYSTEM_DEFAULT_PATH="/bin:/sbin:/usr/bin:/usr/sbin"
earlycon 是 kernel 啟動最早期的輸出通道。沒有它,你看不到任何啟動日誌,等於盲調。
earlycon 只需要一個 putchar() 函數,不需要中斷,純 polling。
NEORV32 UART 寄存器佈局(neorv32_uart.h):
offset 0x00: CTRL 寄存器 (32-bit)
bit 0: EN (全局使能)
bit 18: TX_EMPTY (TX FIFO 空)
bit 19: TX_NFULL (TX FIFO 未滿)
bit 16: RX_NEMPTY (RX FIFO 非空)
bit 31: TX_BUSY (發送忙)
offset 0x04: DATA 寄存器 (32-bit)
bit [7:0]: 讀 = 收到的字節, 寫 = 要發送的字節
earlycon 驅動實現 — 新建 drivers/tty/serial/earlycon-neorv32.c:
// SPDX-License-Identifier: GPL-2.0
/*
* Early console driver for NEORV32 UART
*/
#include <linux/console.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#define NEORV32_UART_CTRL 0x00
#define NEORV32_UART_DATA 0x04
#define UART_CTRL_TX_NFULL (1 << 19)
static void neorv32_earlycon_putchar(struct uart_port *port, unsigned char ch)
{
/* 等待 TX FIFO 有空位 */
while (!(readl(port->membase + NEORV32_UART_CTRL) & UART_CTRL_TX_NFULL))
;
writel(ch, port->membase + NEORV32_UART_DATA);
}
static void neorv32_earlycon_write(struct console *con,
const char *s, unsigned int n)
{
struct earlycon_device *dev = con->data;
uart_console_write(&dev->port, s, n, neorv32_earlycon_putchar);
}
static int __init neorv32_earlycon_setup(struct earlycon_device *dev,
const char *options)
{
if (!dev->port.membase)
return -ENODEV;
dev->con->write = neorv32_earlycon_write;
return 0;
}
EARLYCON_DECLARE(neorv32, neorv32_earlycon_setup);
OF_EARLYCON_DECLARE(neorv32, "neorv32,uart", neorv32_earlycon_setup);完整驅動需要實現 struct uart_ops。初期用純 polling 方式,不用中斷:
新建 drivers/tty/serial/neorv32_uart.c:
// SPDX-License-Identifier: GPL-2.0
/*
* NEORV32 UART serial driver (polling mode)
*/
#include <linux/console.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/serial_core.h>
#include <linux/tty_flip.h>
#define DRIVER_NAME "neorv32-uart"
#define DEV_NAME "ttyNEO"
#define NEORV32_UART_NR 1
/* 寄存器偏移 */
#define NEORV32_UART_CTRL 0x00
#define NEORV32_UART_DATA 0x04
/* CTRL 寄存器位 */
#define CTRL_EN (1 << 0)
#define CTRL_PRSC_SHIFT 3
#define CTRL_PRSC_MASK (0x7 << CTRL_PRSC_SHIFT)
#define CTRL_BAUD_SHIFT 6
#define CTRL_BAUD_MASK (0x3FF << CTRL_BAUD_SHIFT)
#define CTRL_RX_NEMPTY (1 << 16)
#define CTRL_RX_FULL (1 << 17)
#define CTRL_TX_EMPTY (1 << 18)
#define CTRL_TX_NFULL (1 << 19)
#define CTRL_TX_BUSY (1 << 31)
/* DATA 寄存器 */
#define DATA_MASK 0xFF
/* ----- uart_ops callbacks ----- */
static unsigned int neorv32_tx_empty(struct uart_port *port)
{
return (readl(port->membase + NEORV32_UART_CTRL) & CTRL_TX_EMPTY)
? TIOCSER_TEMT : 0;
}
static void neorv32_set_mctrl(struct uart_port *port, unsigned int mctrl) {}
static unsigned int neorv32_get_mctrl(struct uart_port *port) { return TIOCM_CAR; }
static void neorv32_stop_tx(struct uart_port *port) {}
static void neorv32_stop_rx(struct uart_port *port) {}
static void neorv32_start_tx(struct uart_port *port)
{
struct circ_buf *xmit = &port->state->xmit;
while (!uart_circ_empty(xmit)) {
if (!(readl(port->membase + NEORV32_UART_CTRL) & CTRL_TX_NFULL))
break;
writel(xmit->buf[xmit->tail], port->membase + NEORV32_UART_DATA);
uart_xmit_advance(port, 1);
}
}
static int neorv32_startup(struct uart_port *port)
{
/* UART 已由 U-Boot 初始化,here 只需確保 EN 位 */
uint32_t ctrl = readl(port->membase + NEORV32_UART_CTRL);
writel(ctrl | CTRL_EN, port->membase + NEORV32_UART_CTRL);
return 0;
}
static void neorv32_shutdown(struct uart_port *port) {}
static void neorv32_set_termios(struct uart_port *port,
struct ktermios *new,
const struct ktermios *old)
{
/* 暫時不支持動態改 baud rate,使用 U-Boot 設好的 115200 */
unsigned int baud = uart_get_baud_rate(port, new, old, 9600, 115200);
uart_update_timeout(port, new->c_cflag, baud);
}
static const char *neorv32_type(struct uart_port *port)
{
return "NEORV32_UART";
}
static void neorv32_config_port(struct uart_port *port, int flags)
{
port->type = PORT_UNKNOWN; /* 或定義一個 PORT_NEORV32 */
}
/* polling 定時器回調:檢查 RX */
static void neorv32_poll_get_char(struct uart_port *port)
{
uint32_t ctrl = readl(port->membase + NEORV32_UART_CTRL);
if (ctrl & CTRL_RX_NEMPTY) {
uint32_t data = readl(port->membase + NEORV32_UART_DATA);
unsigned char ch = data & DATA_MASK;
uart_insert_char(port, 0, 0, ch, TTY_NORMAL);
tty_flip_buffer_push(&port->state->port);
}
}
static const struct uart_ops neorv32_uart_ops = {
.tx_empty = neorv32_tx_empty,
.set_mctrl = neorv32_set_mctrl,
.get_mctrl = neorv32_get_mctrl,
.stop_tx = neorv32_stop_tx,
.start_tx = neorv32_start_tx,
.stop_rx = neorv32_stop_rx,
.startup = neorv32_startup,
.shutdown = neorv32_shutdown,
.set_termios = neorv32_set_termios,
.type = neorv32_type,
.config_port = neorv32_config_port,
};
/* ... platform driver probe/remove + console registration ... */
/* 這裡省略了完整的 platform_driver 註冊代碼 */
/* 你需要參考 drivers/tty/serial/uartlite.c 作為模板 */Tip
推薦參考的 Linux serial 驅動(結構簡單、適合學習):
drivers/tty/serial/uartlite.c— Xilinx UARTLite,最簡單的 UART 驅動drivers/tty/serial/liteuart.c— LiteX UART,也是 FPGA 軟核的驅動- U-Boot 的
drivers/serial/serial_neorv32.c— NEORV32 UART 在 U-Boot 中的實現
NEORV32 的 FIRQ(Fast Interrupt Request)系統:
- 16 個通道,通過
mieCSR 的 bit [16..31] 控制啟用 - 通過
mipCSR 的 bit [16..31] 檢測 pending mcause的異常碼:FIRQ #n =0x80000010 + n- UART0 = FIRQ #2(
miebit 18,mcause=0x80000012)
初期先跳過中斷驅動,全部用 polling。 等 Linux 成功啟動到 shell 後,再添加 FIRQ irqchip 驅動以提升性能。
# 假設 kernel 源碼在 buildroot output 中
KERN_SRC=${BUILDROOT}/output/build/linux-6.6/
# 複製驅動文件
cp earlycon-neorv32.c $KERN_SRC/drivers/tty/serial/
cp neorv32_uart.c $KERN_SRC/drivers/tty/serial/修改 $KERN_SRC/drivers/tty/serial/Makefile:
obj-y += earlycon-neorv32.o
obj-y += neorv32_uart.o修改 $KERN_SRC/drivers/tty/serial/Kconfig:
config SERIAL_NEORV32
bool "NEORV32 UART support"
depends on RISCV
select SERIAL_CORE
help
NEORV32 RISC-V SoC UART driver.
cd ${BUILDROOT}
# 如果使用外部樹:
# make BR2_EXTERNAL=${PROJECT_DIR} neorv32_ax301_defconfig
# 或者手動配置
make menuconfig
# 設置 kernel 配置、DT 路徑等
make -j$(nproc)構建産物:
output/images/Image— Linux kernel 二進制(~1-3 MB)output/images/rootfs.cpio— 根文件系統(BusyBox, ~500 KB-1 MB)- 需要自己編譯 DTS → DTB
# 編譯 Device Tree
dtc -I dts -O dtb -o neorv32_ax301.dtb neorv32_ax301.dts記憶體佈局規劃:
0x40000000 : U-Boot(已佔用,boot 後不再需要)
0x40200000 : Linux kernel Image
0x40F00000 : DTB (Device Tree Blob)
0x41000000 : initramfs (rootfs.cpio)
方法 A:使用 U-Boot loady + bootm
# 終端 1:minicom 連接板子
minicom -b 115200 -D /dev/ttyUSB0
# 在 U-Boot> 提示符下:
U-Boot> loady 0x40200000 # 然後用 minicom 的 ymodem 傳送 Image
U-Boot> loady 0x40F00000 # 傳送 neorv32_ax301.dtb
U-Boot> loady 0x41000000 # 傳送 rootfs.cpio
# 啟動
U-Boot> bootm 0x40200000 0x41000000 0x40F00000方法 B:修改 boot_uboot.py 自動化
擴展你的 boot_uboot.py,在 U-Boot prompt 後自動發送 kernel:
# 在 U-Boot> 後:
# 1. 通過 U-Boot 的 loady 命令 + ymodem 發送 Image
# 2. 發送 DTB
# 3. 發送 initramfs
# 4. 發送 bootm 命令啟動方法 C:將 initramfs 編入 kernel
最簡單的方式 — 在 kernel 配置中指定 initramfs 源:
CONFIG_INITRAMFS_SOURCE="/path/to/rootfs.cpio"
這樣只需傳送 一個文件(kernel Image 已包含 rootfs),加上一個 DTB:
U-Boot> loady 0x40200000 # Image (含 initramfs)
U-Boot> loady 0x40F00000 # DTB
U-Boot> bootm 0x40200000 - 0x40F00000
[ ] earlycon 有輸出(最早的 "Booting Linux..." 字樣)
[ ] 看到 "Linux version X.X.X ..."
[ ] 看到 "Machine model: NEORV32 on AX301"
[ ] 看到 "Memory: 32MB ..."
[ ] 看到 "CLINT: ..." 定時器初始化
[ ] 看到 "console [ttyNEO0] enabled"
[ ] 看到 "Freeing unused kernel memory..."
[ ] 看到 "~ #" BusyBox shell prompt 🎉
| 現象 | 可能原因 | 解決方案 |
|---|---|---|
| 完全無輸出 | earlycon 未工作 | 檢查 DT chosen/stdout-path,確認 earlycon= 參數 |
| 亂碼 | baud rate 不對 | 確認 kernel 和 UART 都是 115200 |
| 卡在 "Booting Linux..." | kernel entry point 錯誤 | 檢查 CONFIG_PHYS_RAM_BASE,確認 Image 加載地址 |
| "Unable to handle kernel paging request" | 記憶體地址配置錯誤 | 檢查 DT memory 節點,CONFIG_PAGE_OFFSET |
| 定時器不工作(卡住不動) | CLINT 地址或佈局問題 | 在 U-Boot 中 md 0xFFF4BFF8 確認 MTIME 在跑 |
| "Kernel panic - not syncing: No init found" | initramfs 未正確加載 | 用方法 C 編入 kernel |
| 非常慢(幾分鐘沒反應) | 無 DCACHE | 回到 Phase 1.3 啟用 DCACHE |
寫 irq-neorv32-firq.c irqchip 驅動,使 UART RX 可以用中斷而非 polling。
- RTL 中啟用
IO_SPI_EN => true - 寫/移植 SPI 驅動
- 掛載 FAT 文件系統
- 從 SD 卡加載 kernel(告別 xmodem)
- 調整 ICACHE/DCACHE 大小
- 嘗試啟用
CACHE_BURSTS_EN(需修改 SDRAM 控制器支持 burst) - 增大 UART FIFO
Important
-
DCACHE 資源:你願意先嘗試啟用 DCACHE 看 Quartus 能否編譯通過嗎?如果 M9K 不夠,是否願意把 IMEM 從 8 KB 縮小到 4 KB?(stage2_loader 是否能放進 4 KB?)
-
Kernel 版本:Buildroot 2024.02 默認 kernel 6.6 LTS。你有偏好的 kernel 版本嗎?
-
啟動方式:你偏好哪種方式傳送 kernel 到板子?
- (A) 手動用 minicom + ymodem(最靈活,但慢)
- (B) 修改 boot_uboot.py 自動化(一鍵啟動)
- (C) initramfs 編入 kernel,只傳一個文件
- 在 QEMU 上用 Buildroot 的
qemu_riscv32_nommu_virt_defconfig構建並啟動 - 在 QEMU 中執行
cat /proc/cpuinfo,free,ps,ls /確認功能正常
- 修改 RTL 後用
quartus_sh --flow compile編譯,檢查資源使用率 - 燒寫新 bitstream 後確認 U-Boot 仍正常
- 在 U-Boot 中用
md驗證 CLINT MTIME 遞增 - 加載 kernel + DTB,觀察 earlycon 輸出