B5 B阶段流片准备与考核

恭喜你! 你已经基本上达成B阶段的流片指标了. 接下来你还需要进行一些流片相关的准备工作.

流片前的准备工作

切换到32位ysyxSoC

SoC团队已经提供了32位的流片SoC, 我们也已经把ysyxSoC修改成32位, 以帮助大家在接入流片SoC之前在本地环境中进行测试. 和之前的64位ysyxSoC相比, 32位ysyxSoC主要将总线的数据宽度改为32位, 并去除了AXI数据位宽转换模块.

切换到32位ysyxSoC

如果你在2024/07/26 13:00:00之后克隆ysyxSoC项目, 你无需进行任何操作; 否则, 请进行以下步骤:

  1. 执行以下命令获取新的32位ysyxSoC:
    cd ysyxSoC
    git pull origin master # 你可能需要解决一些代码冲突
    
  2. 将NPC顶层的AXI数据位宽修改为32位, 并去除相关的数据位宽转换代码
  3. 重新使用32位的ysyxSoC环境进行仿真

开放NPC的地址空间

流片环境所使用的SoC可能会集成比ysyxSoC更多的设备. 如果NPC提前拦截了相应地址空间的访问, NPC将无法在流片后访问这些设备. 因此, 我们建议你在NPC中开放所有的地址空间, 让所有地址的读写请求都通过NPC对外的AXI总线接口发送出去. 如果访问了不存在设备的地址空间, SoC将会通过AXI的resp信号向NPC返回错误信息.

去除下降沿触发的时钟

上升沿和下降沿混用, 会导致时序收敛更加困难, 增加后端物理实现的难度. 如果你的处理器严重影响SoC整体的时序, 在流片时间节点紧张的情况下, "一生一芯"项目组将会把你的处理器移出该批次的流片名单.

去除下降沿触发的时钟

你需要检查你的代码中是否包含下降沿触发的时钟, 若有, 你需要修改相应模块中的代码去除它们.

去除锁存器

在同步时序逻辑电路中, 所有存储单元的访问都受时钟控制. 但锁存器的写入不受时钟驱动, 时序分析工具难以对其进行分析, 因此一般不在同步时序逻辑电路中使用.

去除锁存器

你需要通过yosys对你的设计进行综合, 然后检查synth_stat.txt中是否包含以下标准单元: DLL_X1, DLL_X2, DLH_X1, DLH_X2. 若有, 说明你的设计中存在锁存器, 你需要修改相应模块中的代码去除它们.

Chisel福利

Chisel生成的Verilog代码描述的都是同步时序逻辑电路, 如果你使用Chisel开发, 你不必担心会生成锁存器.

命名修改

我们会将多个同学的代码集成到同一个SoC中, 如果代码的模块名相同, 会导致EDA工具报告模块重复定义的错误. 为了解决这个问题, 我们要求大家进行以下修改:

  1. 将CPU代码合并到一个.v文件, 文件名为ysyx_8位学号.v, 如ysyx_22040228.v
    • 在Linux上可通过cat命令实现:
      $> cat CPU.v ALU.v regs.v ... > ysyx_22040228.v
      
  2. 将CPU顶层命名修改为ysyx_8位学号,如ysyx_22040228
  3. 为CPU内所有用define定义的变量添加前缀ysyx_8位学号_, 如 `define SIZE 5需要修改为 `define ysyx_22040228_SIZE 5; 如果你使用Chisel, 你不必进行这部分的修改
  4. 为CPU内的所有模块名添加前缀ysyx_8位学号_, 如module ALU修改为module ysyx_22040228_ALU

Chisel福利(2)

如果你使用Chisel开发, 可以通过给Chisel代码添加Annotation来自动添加模块名前缀, 具体方式可参考这个issue中的讨论在新窗口中打开. 如果你使用Verilog/SystemVerilog开发, 目前暂时无法进行模块名前缀的自动添加, 请手动进行添加.

命名修改

根据上述要求, 完成命名修改的工作.

代码静态检查

Verilator可以对verilog进行代码静态检查, 并提示代码中潜在的错误风险之处, 修复这些问题有助于提升代码的正确性. 具体地, 可以通过verilator的--lint-only选项来进行上述检查.

为了让verilator进行尽可能多的检查工作, 可以添加-Wall-Werror选项. verilator的手册在新窗口中打开记录了所有warning的含义, 原则上来说, 你需要尽可能清除它们, 来让你的代码变得更规范, 除了以下两种warning:

  1. DECLFILENAME的warning与代码逻辑无关, 可通过添加-Wno-DECLFILENAME来忽略它.
  2. 对于UNUSED相关的warning, 有的是因为输入端口未使用, 如顶层的io_slave_arvalid, 这部分warning可以忽略; 有的可能是因为你的代码编写不规范, 你需要仔细确认其原因. 如果你决定忽略一个warning, 你需要清楚这可能会引起什么问题, 并对你自己的决定负责.

Chisel福利(3)

Chisel生成的Verilog代码通常是符合规编码规范的, 但也会出现信号和位宽相关的UNUSED warning. 如果你使用Chisel开发, 在这部分工作中你会稍微轻松一些, 但我们仍然建议你仔细核对每个warning.

四值仿真

系统的启动分冷启动和热启动, 其中冷启动是指系统在断电状态下进行上电和复位, 而热启动则是指在上电状态下直接进行复位. 我们知道电路的状态由时序逻辑元件的状态决定, 因此我们期望电路在复位后能进入一个预期的正确状态, 然后从这个状态开始工作. 要实现这一点, 就需要对时序逻辑元件指定复位值.

一个方案是对所有时序逻辑元件进行复位, 保证无论是冷启动还是热启动, 系统都能在复位后进入完全一致的状态. 但对于包含随机存储器的电路来说, 这是无法做到的. 例如, DRAM存储颗粒中的存储阵列没有复位功能, 即使是可以集成在CPU中的SRAM, 其存储阵列也没有复位功能. 这意味着, 在热启动的场景下, 随机存储器中存储阵列的状态不受复位信号的影响, 其中已经存储的数据会被带进下一次复位中. 因此, 电路必须保证, 无论复位后随机存储器中存储什么数据, 电路都应该能正确工作. 换句话说, 电路复位时的行为必须设计成与随机存储器所存储的内容无关.

如果不考虑随机存储器, 剩下的时序逻辑元件就是触发器. 对所有触发器进行复位固然可以使得系统以更大的概率进入正确的状态, 但这也可能会带来额外的延迟和一定的面积开销. 一方面, 布线时需要将复位信号传递到所有触发器中, 因此复位信号的传播延迟也会更高; 另一方面, 触发器的复位功能也需要占用一定的门电路, 这些门电路也会占用一定的面积.

在真实的项目中, 一般是在保证复位后能正确工作的条件下, 仅仅对触发器的一个最小子集进行复位. 至于那些不需要进行复位的触发器, 它们的状态不受复位信号的影响, 因此系统的复位信号到来时, 它们仍然保留之前的状态. 只要一个触发器存储的值不影响电路在复位后的行为, 它就不需要进行复位.

通常来说, 不需要进行复位的触发器主要有两类:

  1. 软件在使用之前可对其初始化的寄存器. 这意味着这些寄存器需要是软件可见的, 换句话说, 它们是由ISA规范定义的. 例如, 对于除了零寄存器以外的通用寄存器, RISC-V规范约定, 复位后其值是未定义的, 因此软件在读出它们之前, 应该对这些通用寄存器进行写入, 确保其中已经存放一个有意义的值. 类似的还有mepc, RISC-V规范同样约定, 复位后其值是未定义的, 因此软件在读出mepc之前, 要么已经发生过一次异常, 使得硬件已经往mepc中写入有意义的值, 要么软件已经通过CSR指令对其进行过写入操作. 事实上, 相关ISA规范将这些寄存器的初值约定为未定义, 本质上是将相关寄存器的初始化从硬件层面移动到软件层面, 从而优化了电路的面积和延迟.
  2. 在数据通路上与控制信号关联的触发器. 通常来说, 类似valid, ready, en等控制信号会用于指示相应的数据信号是否有效, 如果无效, 这些控制信号所连接的下游模块一般不会使用或存储相应的数据信号. 因此, 数据通路上的大部分触发器可以不进行复位, 只要保证电路在产生与其关联的控制信号时, 相应的触发器已经写入了有效的数据即可. 一个例子是流水段寄存器中和存放负载的触发器, 我们只需要对相应的valid寄存器复位即可. 不过具体情况还需要根据你的实现来决定.

对不需要进行复位的触发器进行了复位, 最多是浪费了面积, 增加了复位信号的传播延迟; 但如果没有对需要复位的触发器进行复位, 就可能会使得电路在复位后无法正确工作. 因此, 后者的情况是需要避免的. 之前我们一直使用verilator进行仿真, 它是一个二值仿真工具, 所有信号的值只包含01, 因此所有触发器在复位时都有一个具体的值, 不能很好地检查上述问题.

而在支持四值仿真的RTL仿真器中, 可以通过不定态X来表示一个无复位端触发器的初值. X信号参与门电路运算时, 结果也可能会是X信号, 因此它可以在电路中进行传播. 如果存在一个需要复位但未进行复位的触发器, X信号就会在电路中大范围传播, 使得其他触发器的值也为X, 无法精确表示一个信号的值, 最终使得电路收敛到一个与预期的正确状态不同的另一个状态, 处理器上的程序将无法得到预期的运行结果. 相反, 如果不存在这样的触发器, X信号在电路中的传播就会被控制信号遏制, 并随着有效数据的更新, X信号将会逐渐减少, 最终使得电路收敛到预期的正确状态.

iverilog是一个支持四值仿真的RTL仿真器, 我们可以使用它来检查是否存在需要复位但还未进行复位的触发器. 在使用iverilog之前, 你需要先安装它:

apt-get install iverilog

安装后, 可以先根据相关文档在新窗口中打开运行一个简单的示例.

我们的目标是检查NPC能否在iverilog上运行程序, 而且因为ysyxSoC不参与流片, 因此, 我们只需要单独仿真NPC, 在其上运行riscv32e-npc的AM程序即可. 不过由于iverilog暂不支持DPI-C, 也不支持通过C++代码来驱动仿真, 因此我们需要对仿真环境进行一些改动:

  • 去掉DiffTest和存储器相关的DPI-C调用.
  • 让NPC从0x80000000开始执行程序, 可考虑但不限于以下方法:
    • 将PC的复位值改为0x80000000
    • 将PC的复位值保持0x30000000, 但在0x30000000的位置放置若干指令, NPC通过这些指令马上跳转到0x80000000, 从而执行riscv32e-npc的AM程序
  • 在AXI接口的SRAM中重新用Verilog实现存储阵列的读写功能, 由于这个SRAM仅在仿真过程中使用, 因此你不必考虑相关代码是否可综合, 只需要采用行为建模的方式来实现即可.
  • 通过$readmemh()对上述存储器进行初始化, 将指定的存储器镜像文件读入存储器中. $readmemh()所支持的文件格式可以通过objcopy命令和参数-O verilog生成, 不过$readmemh()认为存储器的地址从0开始, 这与ELF文件的链接地址不同, 因此你还需要用到参数--adjust-vma, 具体用法可参考man objcopy.
  • 编写一个简单的仿真顶层, 在其中实例化时钟和复位信号, 并驱动NPC的仿真.

使用iverilog进行编译时, 会自动定义宏__ICARUS__, 因此你可以把上述改动用条件编译语句包含起来, 从而使得你的代码同时支持verilator和iverilog进行仿真.

完成上述修改后, 就可以尝试用iverilog进行编译了. 不过iverilog支持的语法比verilator要少, 我们建议你通过-g2012选项来采用较新的语言标准, 但你还是可能会遇到verilator编译通过, 但iverilog编译不通过的情况, 你需要根据报错信息修改你的代码. 特别地, 如果你使用Chisel, 你还需要给firtool添加选项 --lowering-options=disallowLocalVariables,disallowPackedArrays, 来屏蔽一些iverilog不支持的语言特性. 此外, 你可能还会遇到以下报错信息, 该信息可忽略:

sorry: constant selects in always_* processes are not currently supported (all bits will be included).

由于无法使用DiffTest, 如果仿真结果与预期不符, 你需要通过波形来诊断问题. 因此, 我们建议你先通过verilator和DiffTest排除尽可能多的问题, 然后再使用iverilog进行仿真. 关于使用iverilog生成波形文件的具体方式, 可以参考上述iverilog文档.

通过四值仿真检查X信号传播问题

在iverilog中仿真NPC并运行microbench和RT-Thread等程序. 如果程序运行失败, 你需要修改相应的RTL代码来解决X信号的传播问题.

网表仿真

由于Verilog本质上是一门事件驱动的建模语言, 可综合的Verilog只是其中一个子集, 因此Verilog仿真器可能会接受一些不可综合的代码. 这并不是Verilog仿真器的bug, 它只是按照Verilog标准手册在新窗口中打开中定义的行为来进行仿真. 显然, 在RTL设计中, 我们并不希望编写出这样的代码.

为了检查项目中是否包含不可综合代码, 一个方案是对综合后的电路进行仿真. 如果项目中包含不可综合代码, 综合后的电路可能与综合前不一致, 从而帮助我们发现相关错误. 因此, 我们可以考虑对yosys的综合结果进行仿真.

综合的步骤是将RTL代码转换成逻辑等价的标准单元, 描述这些标准单元之间的连接关系的文件称为网表. 在网表中, 标准单元是以模块的方式进行实例化的, 因此, 要对网表进行仿真, 就需要提供这些标准单元的模块级实现. 事实上, yosys-sta项目中已经提供了相应的实现, 具体位于yosys-sta/nangate45/sim/cells.v.

网表仿真

用综合后的网表文件和上述标准单元的模块级实现进行仿真, 尝试NPC上运行microbench和RT-Thread等程序. 由于AXI接口以外的存储器不属于流片的范畴, 因此你可以继续采用四值仿真时使用的存储器代码. 此处建议继续采用iverilog进行仿真.

Chisel福利(4)

Chisel生成的Verilog代码都是可综合的, 如果你使用Chisel开发, 网表仿真大概率会直接成功, 但我们仍然建议你不要因此跳过网表仿真.

再次检查你的代码

如果你在上述过程中修改了代码, 请重新进行上述检查, 避免在修改代码的途中再次引入了不符合要求的代码.

申请代码调试考核

流程可能还会有变化

目前申请流程处于内测阶段, 流程可能会根据内测结果有所更新, 请保持关注.

在申请代码调试考核之前, 你需要按照我们要求的方式提交你的代码, 我们将使用CI流程对你的代码进行一系列检查, 当你的代码通过所有检查后, CI流程会自动为你申请代码调试考核. 由于CI流程是自动化的, 当检查失败时, 你可以查阅相应的日志, 检查出错的原因, 修复后再次提交. 如果CI流程因仿真运行结果不符合预期而报错, CI流程不会提供出错现场和波形等信息, 因此我们建议你在提交代码之前先在本地进行较为完善的测试和调试.

由于CI流程是自动化的, 因此它对目录结构, 文件命名, make目标等方面存在一些约定, 你需要根据下文的要求对你的项目进行一些调整, 从而使得CI流程可以在满足约定的情况下对你的代码开展检查. 不过, 这些调整可能会影响你之前搭建的仿真流程, 如果你觉得影响过大, 你可以在ysyx-workbench中创建一个新分支, 调整后单独提交新分支的代码即可.

完成这些调整后, 我们会要求你将这个新分支推送到一个公开的仓库中, 然后通过指定的方式向CI流程提供仓库URL和分支名, CI流程将根据这些信息拉取你的代码, 并开展检查工作.

1. 目录结构的约定

你的ysyx-workbench需要包含以下内容:

ysyx-workbench
├── abstract-machine
├── nemu
├── npc
├── Makefile
└── patch
    ├── rt-thread-am
    │   ├── 0001.aaa.patch
    │   ├── 0002.bbb.patch
    │   └── ...
    └── ysyxSoC
        ├── 0001.xxx.patch
        ├── 0002.yyy.patch
        └── ...

其中:

  • ysyx-workbench可以更换名称, CI流程从远程仓库拉取你的代码时, 会自动将工程目录统一更名为ysyx-workbench. 因此, 我们对远程仓库的名称不作要求.
  • abstract-machine, nemunpc不得更换名称.
  • Makefile不得更换名称, 其中STUIDSTUNAME需要正确设置.
  • 考虑到rt-thread-amysyxSoC这两个项目, 你需要进行的修改并不多, CI流程通过打补丁的方式来获取你的修改. 具体地, 你需要在patch目录下存放rt-thread-amysyxSoC的补丁, CI流程会从上游仓库重新拉取相应代码, 并分别应用patch目录下的补丁, 从而得到与你本地工程一致的状态. 补丁的生成方式见下文的介绍.
  • 考虑CI流程的测试过程不依赖于你对am-kernels, nvboardyosys-sta这些项目的修改, 因此, CI流程将从上游仓库重新拉取相应代码. 这意味着, CI流程并不会获取你对这些项目的任何改动.

2. make目标的约定

CI流程对make目标有如下约定:

  • make -C npc verilog会在npc/build/目录下生成名为 ysyx_xxxxxxxx.vysyx_xxxxxxxx.sv的文件, 用于接入SoC, 因此PC复位值为0x30000000, 其中xxxxxxxx为8位"一生一芯"学号. CI流程会将这个文件作为后续仿真和综合的输入. 若目录下同时存在.v.sv两个文件, CI流程会优先选择.sv文件.
  • CI流程会依次运行make -C ysyxSoC dev-initmake -C ysyxSoC verilog, 来生成ysyxSoCFull.v. 其中, 运行make -C ysyxSoC verilog后, CI流程假设ysyxSoC/build/ysyxSoCFull.vysyx_0000000000000000已经被修改为你的学号.
  • am-kernels中的代码目录下运行make ARCH=riscv32e-npc, 会生成一个可以运行在未接入SoC时的NPC的程序, 程序的第一条指令位于0x80000000.
  • am-kernels中的代码目录下运行make ARCH=riscv32e-ysyxsoc run, 会生成一个可以运行在接入SoC后的NPC的程序, 并启动仿真过程来运行这个程序, 程序的第一条指令位于0x30000000.
  • make -C npc sim-iverilog IMG=xxx会用iverilog对上述ysyx_xxxxxxxx.vysyx_xxxxxxxx.sv进行仿真, 并运行程序xxx. 程序的第一条指令位于0x80000000. 注意, 此处的xxx.bin文件, 但readmemh()无法读入.bin文件, 你可以在这条规则中添加命令, 来从.bin文件生成readmemh()所支持文件, 然后再进行仿真.
  • make -C npc sim-iverilog-netlist IMG=xxx NETLIST=yyy CELLS=zzz会用iverilog对网表文件yyy进行仿真, 并运行程序xxx, 程序的第一条指令位于0x80000000. 其中, yyy为通过yosys-sta项目对上述ysyx_xxxxxxxx.vysyx_xxxxxxxx.sv进行综合后得到的网表文件, zzz为标准单元的行为仿真文件的路径, 它会被加到仿真源文件的列表中. 同样地, 此处的xxx.bin文件.

此外, CI流程不会对DiffTest的功能进行测试, 因此我们建议你关闭DiffTest. 如果你想打开DiffTest, 你需要执行相应make目标的过程中插入编译NEMU的命令. 除了框架代码本身提供的make目标之外, CI流程不会执行除了上述目标以外的make目标.

我们也建议你关闭波形功能, CI流程不会给你返回生成的波形. 打开波形功能除了降低仿真速度之外, 并不会带来其他变化. 因此, 不要把CI流程作为一个调试平台, 你应该在本地进行充分的测试和调试.

3. 补丁的生成方式

如上文所述, CI流程通过打补丁的方式来获取你对rt-thread-amysyxSoC这两个项目的修改. 你需要通过git format-patch命令手动生成补丁. 例如

cd rt-thread-am
git format-patch origin/master

上述命令将进入rt-thread-am项目的目录, 然后生成一系列命名形如0001-xxx.patch的补丁文件, 每个文件对应一次git的提交, 这些补丁文件依次记录了从项目上游的master开始, 到当前本地分支的HEAD为止的所有提交.

你需要将这些补丁文件移动到ysyx-workbench/patch/rt-thread-am/目录下, CI流程会从rt-thread-am上游的master分支开始, 依次应用这些补丁, 从而得到与你本地工程一致的状态.

4. 仓库结构的约定

按上述要求准备好ysyx-workbench后, 将其推送到一个公开的仓库中, 分支名可随意设置. 除此之外, 你还需要将ysyx-workbenchtracer-ysyx分支推送到同样的仓库中. 也即, 仓库中至少包含两个分支, 一个是满足上述目录结构要求的分支, 分支名随意; 另一个是由git追踪系统自动维护的分支, 分支名为tracer-ysyx.

上述仓库可以使用任意代码托管平台, 只要能使用git clone命令通过https方式拉取到上述两个分支即可.

5. 将学号添加到白名单

上述工作准备完成后, 请联系助教, 将你的学号加入CI流程的白名单.

6. 填写考核申请表单

接下来你需要填写考核申请表单. 访问如下仓库:

https://github.com/未发布

创建一个新的issue, 选择考核模板后, 按如下指导填写相应内容:

  • 标题 - 为了方便后续搜索, 建议issue标题中包含你的"一生一芯"学号. 申请考核的同学较多时, 你可以通过学号搜索到自己创建的issue.
  • 一生一芯学号 - 8位学号, 不包含ysyx_等字符
  • 仓库URL - 上文描述的仓库的链接
  • 分支名 - 指示满足上述目录结构要求的分支
  • 注释 - 用于标识一次issue提交, 可作任意标记, 如"第5次提交", "修改了目录"等. CI流程会在返回的结果中附带填写的注释, 帮助你区分多次issue提交.

提交issue后会激活CI流程, 它将根据我们设定好的脚本运行各种检查. CI流程启动后, 会自动在issue下方进行评论, 评论内容包括时间戳, 填写的注释, 以及工作流URL. 你可以点击工作流URL查看CI流程的执行情况.

如果CI流程运行成功, 评论将会追加一行Finish, 然后关闭issue. 此时, 你的代码已经通过了CI流程的检查, 可以进行代码调试考核的后续步骤. 如果CI流程运行出错, 评论将会追加一行End with errors. 你可以点击工作流URL, 查看出错的任务和步骤, 根据报错信息修改并上传你的代码. 然后编辑issue, 更新注释, 提交后触发新一轮CI流程进行检查.

如果CI流程运行成功, CI流程会以ysyx_学号的分支名将你的代码推送到我们的仓库中. 如果分支已存在, 则会因推送失败而出错. 也即, 当前流程不允许覆盖已经通过CI流程检查的代码, 因此只允许1次成功的CI流程. 如果需要更新已经通过CI流程检查的代码, 请联系助教并说明原因.

CI流程执行结束需要花费半小时以上. 当提交申请的同学较多时, CI流程中的more-tests任务需要排队等待. CI流程中的一个任务默认最多执行6小时, 若超时, 该任务将会被强制取消. 如果任务因排队等待较长时间而取消, 请等待一段时间后, 通过编辑issue来触发新一轮CI流程进行重试.

代码调试考核步骤

建设中

最近更新时间:
贡献者: Zihao Yu