# ============================================================================= # build.ps1 —— 扫雷程序一键构建:C -> 汇编/目标文件 -> ELF -> .hex + .vh # # 用法(在 saolei\ 目录下): # powershell -ExecutionPolicy Bypass -File tools\build.ps1 # powershell -ExecutionPolicy Bypass -File tools\build.ps1 -Prefix riscv64-unknown-elf- # # 产物(都在 saolei\build\ 下): # saolei.lst objdump -d 反汇编清单(这就是"编译出来的汇编",可以直接看) # saolei.elf 可执行文件 # saolei.bin 裸二进制 # saolei.hex $readmemh 格式:拷到 pipelined-cpu\srcs\rom\saolei.hex 就能 # 用现成的测试平台(tb_no_intr 等)仿真 # saolei.map 链接 map(看段地址和大小) # 并且会生成 pipelined-cpu\srcs\board\cpu21_saolei_rom.vh —— 供 cpu21_board_top.v 用 # `include 固化进 ROM(在 Vivado 里定义宏 SAOLEI_ROM 即可切换到扫雷程序) # # 构建流程里有三道针对这颗 CPU 的关卡: # 1) -fno-pic -mcmodel=medlow -mno-relax:避免 auipc / 链接器松弛 # 2) 链接脚本里的 ASSERT:禁止 .data / .rodata(这颗核读不到 ROM 里的数据) # 3) tools\check_isa.py:逐条检查反汇编,凡是用到未实现指令(auipc/lb/lbu/div...) # 直接报错并给出修复提示 # ============================================================================= param( [string]$Prefix = "", [int]$RomWords = 8192, [switch]$SkipIsaCheck ) $ErrorActionPreference = 'Stop' $root = Split-Path -Parent $PSScriptRoot $sw = Join-Path $root 'sw' $build = Join-Path $root 'build' New-Item -ItemType Directory -Force $build | Out-Null # ---------------------------------------------------------------- 工具链探测 if (-not $Prefix) { foreach ($cand in 'riscv-none-elf-', 'riscv64-unknown-elf-', 'riscv32-unknown-elf-', 'riscv-none-embed-', 'riscv64-linux-gnu-') { if (Get-Command ($cand + 'gcc') -ErrorAction SilentlyContinue) { $Prefix = $cand; break } } } if (-not $Prefix) { Write-Output "ERROR: 找不到 RISC-V 交叉编译器(riscv*-gcc)。" Write-Output "" Write-Output "装一个即可(推荐 xPack 的 riscv-none-elf-gcc,解压就用,不用管理员权限):" Write-Output " 1) 打开 https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases" Write-Output " 2) 下载 win32-x64 的 .zip(例如 xpack-riscv-none-elf-gcc-14.2.0-3-win32-x64.zip)" Write-Output " 3) 解压到例如 C:\tools\riscv-none-elf-gcc" Write-Output " 4) 把 C:\tools\riscv-none-elf-gcc\bin 加进 PATH(或者直接运行本脚本时):" Write-Output " powershell -ExecutionPolicy Bypass -File tools\build.ps1 -Prefix 'C:\tools\riscv-none-elf-gcc\bin\riscv-none-elf-'" exit 1 } $GCC = $Prefix + 'gcc' $OBJDUMP = $Prefix + 'objdump' $OBJCOPY = $Prefix + 'objcopy' $SIZE = $Prefix + 'size' $Python = 'py' if (-not (Get-Command $Python -ErrorAction SilentlyContinue)) { $Python = 'python' } Write-Output "工具链前缀: $Prefix" # ---------------------------------------------------------------- 编译参数 # -march=rv32im_zicsr : RV32I + M 扩展 + Zicsr(CSR 指令:csrrw/csrrsi/csrrci)。 # GCC 15 起不再默认给 rv32im 开 zicsr,中断入口里的 # csrrsi 必须显式打开这个扩展才认。 # -mcmodel=medlow -fno-pic : 用 lui/addi 绝对寻址,不产生 auipc # **必须放开链接器松弛**(不要 -mno-relax):gcc 的函数调用默认是 # auipc ra, %pcrel_hi(f) + jalr ra, ra, %pcrel_lo(f) # 要靠链接器松弛把它变成一条 jal,否则 auipc 会留在最终代码里(这颗核没有)。 # 松弛只会在两种情况下改变指令:把 pcrel 调用变成 jal,或在使用 gp 相对寻址时 # 变成 gp 相对访问 —— 后者已被 -msmall-data-limit=0 从根上禁止。 # -msmall-data-limit=0 : 不用 gp 相对寻址 # -fno-builtin -nostdlib -ffreestanding : 不要 libc(没有环境,也没有 printf) # -fno-jump-tables : 不生成跳转表(跳转表会落在 .rodata,这颗核读不到) $ccCommon = @( '-march=rv32im_zicsr', '-mabi=ilp32', '-mcmodel=medlow', '-fno-pic', '-fno-PIC', '-fno-pie', '-msmall-data-limit=0', '-fno-common', '-fno-builtin', '-fno-stack-protector', '-fno-jump-tables', '-ffreestanding', '-nostdlib', '-ffunction-sections', '-fdata-sections', '-Os', '-Wall', '-Wextra' ) # 中断主体(isr.c)必须只使用 callee-saved 寄存器,否则会破坏被打断的代码。 # 下面把所有"调用者保存"的通用寄存器都固定住,GCC 就只能用 s0-s11 和 sp。 $fixedRegs = @() foreach ($r in 1, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 28, 29, 30, 31) { $fixedRegs += ('-ffixed-x' + $r) } # ---------------------------------------------------------------- 编译 $objs = @() # 跑外部命令并在非 0 退出码时立刻停下(native 命令失败不会抛异常,必须查 $LASTEXITCODE) function Invoke-Tool { param([string]$Exe, [string[]]$ArgList, [string]$What) & $Exe @ArgList if ($LASTEXITCODE -ne 0) { Write-Output ("ERROR: 失败 -> " + $What) exit 1 } } Write-Output "编译 start.c ..." Invoke-Tool -Exe $GCC -ArgList ($ccCommon + @('-c', (Join-Path $sw 'start.c'), '-o', (Join-Path $build 'start.o'))) -What 'start.c' $objs += (Join-Path $build 'start.o') Write-Output "编译 saolei.c ..." Invoke-Tool -Exe $GCC -ArgList ($ccCommon + @('-c', (Join-Path $sw 'saolei.c'), '-o', (Join-Path $build 'saolei.o'))) -What 'saolei.c' $objs += (Join-Path $build 'saolei.o') Write-Output "编译 isr.c(额外加 -ffixed 约束)..." Invoke-Tool -Exe $GCC -ArgList ($ccCommon + $fixedRegs + @('-c', (Join-Path $sw 'isr.c'), '-o', (Join-Path $build 'isr.o'))) -What 'isr.c' $objs += (Join-Path $build 'isr.o') # ---------------------------------------------------------------- 链接 $elf = Join-Path $build 'saolei.elf' $map = Join-Path $build 'saolei.map' $ld = Join-Path $sw 'linker.ld' Write-Output "链接 ..." # 注意:PowerShell 会把未加引号的 "-Wl,--no-relax" 当成参数对解析(逗号引起), # 所以这里统一用参数数组,所有选项都带引号。 $linkArgs = @( '-march=rv32im_zicsr', '-mabi=ilp32', '-nostdlib', '-nostartfiles', "-Wl,-Map=$map", '-T', $ld, '-o', $elf ) + $objs Invoke-Tool -Exe $GCC -ArgList $linkArgs -What '链接(-T sw/linker.ld)' # ---------------------------------------------------------------- 反汇编 + ISA 检查 $lst = Join-Path $build 'saolei.lst' & $OBJDUMP -d -M no-aliases $elf | Out-File -Encoding utf8 $lst Write-Output "反汇编清单: $lst" $isaOk = $true if (-not $SkipIsaCheck) { Write-Output "" & $Python (Join-Path $PSScriptRoot 'check_isa.py') $lst if ($LASTEXITCODE -ne 0) { $isaOk = $false } } # ---------------------------------------------------------------- 转 hex / vh $bin = Join-Path $build 'saolei.bin' $hex = Join-Path $build 'saolei.hex' $vh = Join-Path $root '..\pipelined-cpu\srcs\board\cpu21_saolei_rom.vh' & $OBJCOPY -O binary $elf $bin & $Python (Join-Path $PSScriptRoot 'elf2hex.py') $bin $hex $RomWords $vh # ---------------------------------------------------------------- 汇总 Write-Output "" Write-Output "==== 段大小 ====" & $SIZE $elf $isrLine = Select-String -Path $lst -Pattern '^[0-9a-f]+ <(isr1_entry|isr2_entry|isr3_entry|_start)>:' | Select-Object -ExpandProperty Line Write-Output "==== 入口地址 ====" foreach ($l in $isrLine) { Write-Output (" " + $l) } Write-Output " 三个中断入口必须和顶层 cpu21_board_top 的参数一致:" Write-Output " linker.ld 的 .text.isr1/2/3 = IRQ1/2/3_VECTOR = 0x100/0x200/0x300" Write-Output "" if ($isaOk) { Write-Output "构建完成:$hex 与 $vh 已生成。" Write-Output " 仿真:把 saolei.hex 拷到 pipelined-cpu\srcs\rom\saolei.hex,用 tb_no_intr 加载" Write-Output " 上板:cpu21_saolei_rom.vh 已生成到 srcs/board/,Vivado 里定义宏 SAOLEI_ROM 后综合" } else { Write-Output "构建完成,但 ISA 检查没通过(见上面的清单),这个 hex 放到 CPU 上会跑飞。" exit 1 }