Files
risc-v-cpu/pipelined-cpu/create_project.tcl
T
Alpha 4ef3960553 feat(tcl): create_project.tcl 自动清理旧工程后重建
- 新增 clean_first 开关(默认 1):先删掉旧工程与所有生成目录再新建,
  只删生成物(*.xpr/*.srcs/*.runs/*.sim/*.cache/*.hw/*.gen/*.ip_user_files/
  .Xil/xsim.dir/*.lpr/vivado*.jou|log|str/webtalk.*),srcs/、tools/、programs/ 不动
- 被别的 Vivado 占用的工程目录删不掉时,给出明确提示并中止(不再抛 Vivado 原始异常)
- create_project 加 -force,残留目录也能覆盖
- 实测:空目录建工程成功;连续第二次运行自动清理 7 项后重建成功,综合通过
2026-09-19 16:26:15 +08:00

234 lines
12 KiB
Tcl
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ============================================================================
# cpu21-riscv 流水线 CPU —— 一键重建 Vivado 工程
# FPGA:Nexys4DDR / xc7a100tcsg324-1(Vivado 2020.2 实测:建工程 + 综合均通过)
#
# 目录结构(本脚本所在目录就是工程目录):
# create_project.tcl 本脚本
# srcs/rtl/*.v 处理器 RTL(顶层 cpu21_riscv_redirect_int_bpb)
# srcs/board/*.v|*.vh 板级顶层、片内 ROM 初始化(含 __FAST 仿真版本)
# srcs/constrs/*.xdc 引脚约束与板级母版
# srcs/tb/*.v 测试平台(中断/分支预测、无中断、板级)
# srcs/rom/*.hex 测试平台加载的 ROM 镜像
# tools/、programs/ 迷你汇编器与测试程序(改程序、重生成 ROM 用)
# 说明文档:cpu21_riscv_redirect_int_bpb说明.md
#
# 用法:
# 1) 在 Vivado Tcl Shell 里执行: source create_project.tcl
# 或命令行: vivado -mode batch -source create_project.tcl -log create.log
# 2) 脚本会**先自动清理旧工程**再新建:只删生成物(*.xpr / *.srcs / *.runs /
# *.sim / *.cache / *.hw / *.gen / *.ip_user_files / .Xil / xsim.dir /
# vivado*.jou|log|str / webtalk.*),srcs/、tools/、programs/ 一律不动。
# 不想自动清理(比如想在旧工程上追加设置)就把下面的 clean_first 改成 0。
# 3) 工程就地创建:pipelined-cpu.xpr(源文件仍在 srcs/ 下,不会被搬动)。
# 4) 若提示 .xpr 删不掉,说明有 Vivado(含 Hardware Manager)正开着那个工程,
# 关掉它再跑一遍即可。
#
# 脚本用 [info script] 定位自身目录,所以整个文件夹拷到任何路径都能直接用。
# 若想用 Vivado 自动生成的版本,见说明文档“工程分享”一节。
# ============================================================================
set origin_dir [file dirname [file normalize [info script]]]
set proj_name "pipelined-cpu"
set part_name "xc7a100tcsg324-1"
# ---------------------------------------------------------------- 自检
set required [list \
"$origin_dir/srcs/board/cpu21_board_top.v" \
"$origin_dir/srcs/board/cpu21_board_rom.vh" \
"$origin_dir/srcs/board/cpu21_board_rom_fast.vh" \
"$origin_dir/srcs/constrs/cpu21_board_top.xdc" \
"$origin_dir/srcs/tb/tb_cpu21_riscv_redirect_int_bpb.v" \
"$origin_dir/srcs/tb/tb_no_intr.v" \
"$origin_dir/srcs/tb/tb_board_top.v"]
set missing {}
foreach f $required { if {![file exists $f]} { lappend missing $f } }
if {[llength [glob -nocomplain "$origin_dir/srcs/rtl/*.v"]] == 0} {
lappend missing "$origin_dir/srcs/rtl/*.v"
}
if {[llength $missing] > 0} {
puts "ERROR: 缺少下列文件,请确认 srcs/ 目录完整:"
foreach f $missing { puts " $f" }
return 1
}
# ---------------------------------------------------------------- 清理旧工程
# 一键重建:先删掉旧工程和所有生成目录。工程完全由 srcs/ + 本脚本决定,
# 所以删掉不会丢任何源文件;不想自动清理就把 clean_first 改成 0。
set clean_first 1
if {$clean_first} {
# 必须删掉的工程目录:任何一个删不掉都不能继续(否则 Vivado 会拒绝建工程)
set proj_items [list \
"$origin_dir/$proj_name.xpr" \
"$origin_dir/$proj_name.srcs" \
"$origin_dir/$proj_name.runs" \
"$origin_dir/$proj_name.sim" \
"$origin_dir/$proj_name.cache" \
"$origin_dir/$proj_name.hw" \
"$origin_dir/$proj_name.gen" \
"$origin_dir/$proj_name.ip_user_files" \
"$origin_dir/$proj_name.lpr" \
"$origin_dir/.Xil"]
# 顺带清掉的杂项:删不掉也无所谓(本进程自己会占着 vivado.jou / .log / .str)
set junk_items [list \
"$origin_dir/xsim.dir" \
"$origin_dir/webtalk.jou" \
"$origin_dir/webtalk.log" \
"$origin_dir/usage_statistics_webtalk.html" \
"$origin_dir/usage_statistics_webtalk.xml"]
foreach pat {vivado*.jou vivado*.log vivado*.str} {
foreach f [glob -nocomplain "$origin_dir/$pat"] { lappend junk_items $f }
}
set blocked {}
set removed 0
foreach {group items} [list proj $proj_items junk $junk_items] {
foreach d $items {
if {![file exists $d]} { continue }
if {[catch {file delete -force $d} err]} {
puts " WARNING: 删不掉 [file tail $d]($err)"
if {$group eq "proj"} { lappend blocked [file tail $d] }
} else {
puts " 已删除:[file tail $d]"
incr removed
}
}
}
puts "旧工程清理完成(删除 $removed 项)"
if {[llength $blocked] > 0} {
puts "ERROR: 这些工程目录删不掉:$blocked"
puts " 它们正被另一个 Vivado(GUI / Hardware Manager)占用。"
puts " 请关闭 Vivado 后重跑本脚本。"
return 1
}
} else {
if {[file exists "$origin_dir/$proj_name.xpr"] || [file exists "$origin_dir/$proj_name.runs"]} {
puts "ERROR: 工程已存在且 clean_first = 0:$origin_dir/$proj_name.xpr"
puts " 请先删除它,或把 clean_first 改成 1 让脚本自动清理。"
return 1
}
}
# ---------------------------------------------------------------- 建工程
# 只用 -part 建工程,**不依赖"板卡文件"**:Vivado 默认不带 Nexys4DDR 的 board 文件,
# 所以 GUI 新建向导的 Boards 页里找不到它;board 只影响"选板/板级预设",
# 不影响综合与实现(引脚全在 srcs/constrs/*.xdc 里显式约束)。
# 若以后装了 Digilent 板卡文件(data/boards/board_files/nexys4-ddr),想按板卡建:
# create_project $proj_name $origin_dir -part $part_name \
# -board_part digilentinc.com:nexys4ddr:part0:1.1
# (那样工程会记上板卡,别人没装板卡文件就打不开了,所以默认不这么写。)
# -force:万一还有残留目录(例如被占用的 .runs)也能覆盖,不至于卡死。
create_project $proj_name $origin_dir -part $part_name -force
# 设计源文件:处理器 RTL + 板级顶层 + 片内 ROM 初始化
add_files -norecurse -fileset sources_1 [glob "$origin_dir/srcs/rtl/*.v"]
add_files -norecurse -fileset sources_1 [list \
"$origin_dir/srcs/board/cpu21_board_top.v" \
"$origin_dir/srcs/board/cpu21_board_rom.vh"]
set_property file_type {Verilog Header} \
[get_files "$origin_dir/srcs/board/cpu21_board_rom.vh"]
# 默认跑哪个内置程序(同一时间只应启用一个):
# use_intrupt_sllsb = 1 -> 课程新版「多级中断测试 + 附加 SLL/SB/BLTU」
# (programs/risc-v-intrupt-sllsb.asm / .hex)
# use_intrupt_ccab = 1 -> 上一版中断测试(programs/risc-v-intrupt-ccab.*)
# use_saolei = 1 -> 扫雷(saolei/tools/build.ps1 生成)
# 三个都为 0 -> 走马灯 demo
set use_intrupt_sllsb 1
set use_intrupt_ccab 0
set use_saolei 0
# 中断测试程序的 ROM 初始化。
set intrupt_ccab_rom "$origin_dir/srcs/board/risc-v-intrupt-ccab.vh"
if {[file exists $intrupt_ccab_rom]} {
add_files -norecurse -fileset sources_1 [list $intrupt_ccab_rom]
set_property file_type {Verilog Header} [get_files $intrupt_ccab_rom]
} else {
puts "NOTE: 还没有 $intrupt_ccab_rom"
}
# 课程新版中断测试(附加 SLL/SB/BLTU)的 ROM 初始化。
set intrupt_sllsb_rom "$origin_dir/srcs/board/risc-v-intrupt-sllsb.vh"
if {[file exists $intrupt_sllsb_rom]} {
add_files -norecurse -fileset sources_1 [list $intrupt_sllsb_rom]
set_property file_type {Verilog Header} [get_files $intrupt_sllsb_rom]
} else {
puts "NOTE: 还没有 $intrupt_sllsb_rom(用 tools/hex2vh.py 生成)"
}
# 扫雷程序的 ROM 初始化(由 saolei/tools/build.ps1 生成,可能还不存在)。
# 综合/仿真时定义宏 SAOLEI_ROM 就会用它替换上面的跑马灯 ROM。
set saolei_rom "$origin_dir/srcs/board/cpu21_saolei_rom.vh"
if {[file exists $saolei_rom]} {
add_files -norecurse -fileset sources_1 [list $saolei_rom]
set_property file_type {Verilog Header} [get_files $saolei_rom]
} else {
puts "NOTE: 还没有 $saolei_rom —— 需要先跑 saolei/tools/build.ps1 生成"
}
# 关键:sources_1 里定义对应宏,cpu21_board_top.v 的 `ifdef 链才会选中那个 ROM
# (宏只影响综合用的 sources_1;仿真 fileset 仍各自用 tb 自带的 ROM)
if {$use_intrupt_sllsb && [file exists $intrupt_sllsb_rom]} {
set_property verilog_define {RISC_V_INTRUPT_SLLSB_ROM} [get_filesets sources_1]
puts "NOTE: sources_1 已定义 RISC_V_INTRUPT_SLLSB_ROM —— 上板跑「中断测试 + SLL/SB/BLTU」"
} elseif {$use_intrupt_ccab && [file exists $intrupt_ccab_rom]} {
set_property verilog_define {RISC_V_INTRUPT_CCAB_ROM} [get_filesets sources_1]
puts "NOTE: sources_1 已定义 RISC_V_INTRUPT_CCAB_ROM —— 上板跑 risc-v-intrupt-ccab"
} elseif {$use_saolei && [file exists $saolei_rom]} {
set_property verilog_define {SAOLEI_ROM} [get_filesets sources_1]
puts "NOTE: sources_1 已定义 SAOLEI_ROM —— 上板跑扫雷;想跑走马灯把本脚本里 use_saolei 改成 0"
} else {
puts "NOTE: 未定义 SAOLEI_ROM —— 上板跑走马灯 demo"
}
# 约束(cpu21_board_top.xdc 是引脚与分频时钟约束)
add_files -norecurse -fileset constrs_1 [glob "$origin_dir/srcs/constrs/*.xdc"]
# 仿真文件:测试平台 + ROM 镜像 + 快速版 ROM 头文件
add_files -norecurse -fileset sim_1 [list \
"$origin_dir/srcs/tb/tb_cpu21_riscv_redirect_int_bpb.v" \
"$origin_dir/srcs/tb/tb_no_intr.v" \
"$origin_dir/srcs/tb/tb_board_top.v" \
"$origin_dir/srcs/board/cpu21_board_rom_fast.vh"]
add_files -norecurse -fileset sim_1 [glob "$origin_dir/srcs/rom/*.hex"]
set_property file_type {Verilog Header} \
[get_files "$origin_dir/srcs/board/cpu21_board_rom_fast.vh"]
# 第二个仿真 fileset:无中断测试平台(tb_no_intr)
create_fileset -simset sim_2
add_files -norecurse -fileset sim_2 [list \
"$origin_dir/srcs/tb/tb_no_intr.v" \
"$origin_dir/srcs/tb/tb_cpu21_riscv_redirect_int_bpb.v"]
# ---------------------------------------------------------------- 顶层与顺序
set_property top cpu21_board_top [get_filesets sources_1]
set_property top tb_cpu21_riscv_redirect_int_bpb [get_filesets sim_1]
set_property top tb_no_intr [get_filesets sim_2]
update_compile_order -fileset sources_1
update_compile_order -fileset sim_1
update_compile_order -fileset sim_2
# GUI 仿真默认只跑 1000 ns,这里改成跑完(否则看起来像卡住)
foreach fs {sim_1 sim_2} {
catch { set_property -name {xsim.simulate.runtime} -value {all} -objects [get_filesets $fs] }
}
# ---------------------------------------------------------------- 结果汇总
puts "工程已创建:$origin_dir/$proj_name.xpr"
puts " sources_1 顶层 = cpu21_board_top(上板:时钟分频 + 片内 ROM/RAM + 引脚)"
if {$use_intrupt_sllsb && [file exists $intrupt_sllsb_rom]} {
set selected_program {risc-v-intrupt-sllsb (RISC_V_INTRUPT_SLLSB_ROM) 中断+SLL/SB/BLTU}
} elseif {$use_intrupt_ccab && [file exists $intrupt_ccab_rom]} {
set selected_program {risc-v-intrupt-ccab (RISC_V_INTRUPT_CCAB_ROM)}
} elseif {$use_saolei && [file exists $saolei_rom]} {
set selected_program {扫雷 (SAOLEI_ROM)}
} else {
set selected_program {走马灯 demo}
}
puts " sources_1 程序 = $selected_program"
puts " sim_1 顶层 = tb_cpu21_riscv_redirect_int_bpb(中断/分支预测)"
puts " sim_2 顶层 = tb_no_intr(指令集测试,ROM 镜像在 srcs/rom/)"
puts " 下一步:Run Synthesis → Run Implementation → Generate Bitstream,或 Run Simulation"
puts " 提示:CPU 时钟 = 100 MHz / CLK_DIV(默认 2)= 50 MHz;改分频比时"
puts " cpu21_board_top.v 的 CLK_DIV 与 cpu21_board_top.xdc 的 -divide_by 要一起改。"