显示(RTL) - cpu21_vga.v:显存从"每格 1 字节"改成"每格 2 字节"(第 1 字节是 6 位字形、 第 2 字节是 3 位颜色),字地址由 cidx[10:2] 改为 cidx[10:1](1 字 = 2 格); 字模表由 4 位扩到 6 位(0..63),新增 A-Z 大写字母(16..41),原 0..15 的 数字/雷/旗/问号/横线/实心字形不变 - cpu21_mmio.v:显存窗口 0x2000-0x27FF -> 0x2000-0x2FFF(300 -> 600 字), fb_widx 相应取 addr_i[11:2] - cpu21_board_top.v:FB_WORDS 300 -> 600(1200 -> 2400 字节) 软件 - mmio.h:fb_write() -> fb_write_cell(idx, glyph, color)(一格写两个 sb); 删掉 CELL(),新增 G_A..G_Z 字形常量 - saolei.c:draw_cell / clear_screen 改用 fb_write_cell;开始界面显示 MINES / EASY / NORMAL / HARD / START 文字(START 直接写在绿色按钮条上), 不再是"两排雷图案 + 纯色条" - 难度选择:新增 g_difficulty / g_mines,三档 20 / 40 / 60 雷;place_mines、 胜利用的 opened_cnt 比较、toggle_flag 的剩余雷数都改读 g_mines;点难度那一行 即切换并重画菜单,点 START 才把雷数交给关卡 - 全部索引运算仍只用移位/掩码(棋盘固定 16x16),没有引入除法 配色(修"数字 1 看不见") - 数字 1 用 C_BLUE、游戏背景原来也是 C_BLUE,调色板里是同一个深蓝,所以数字 1 完全看不见;现在菜单与关卡背景统一改成 C_BLACK,胜负闪动仍是绿/红/黑, 数字本身的配色不动 工具与文档 - build.ps1:Python 启动器兼容 —— 有 py 用 py,没有就回退到 python (本机只有 python,之前 check_isa.py / elf2hex.py 两处都会中断) - saolei/README.md:显存表(0x2000-0x2FFF、每格 2 字节)、格子编码(6 位字形、 16..41 是 A-Z)、难度说明同步更新 - README.md / cpu21_board_top.v:仅格式化工具重排(Markdown 表格对齐、 if/else 合并成一行),无语义变化 验证:build.ps1 全流程通过(972 条指令 ISA 检查全过;text=3888 B / data=0 / bss=1444 B;入口 _start=0x0、isr1/2/3=0x100/0x200/0x300);xvlog 不带宏与带 SAOLEI_ROM 两种配置编译均无错;tools/check_rtl_text.ps1 体检 OK。 未验证:未跑行为仿真、未上板。
180 lines
8.1 KiB
PowerShell
180 lines
8.1 KiB
PowerShell
# =============================================================================
|
||
# 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
|
||
}
|