给 Codex 装一个 Verilog Skill,笔者用它跑通了 Ready/Valid RTL
摘要
这次不聊抽象的 AI 芯片设计。笔者给 Codex 接入 Erie Verilog Generator v0.4.0,让它生成一个带一级缓存的 Ready/Valid 流水寄存器和自检查 Testbench,然后在 macOS 上用 Icarus Verilog 做真实仿真,再用 Yosys 检查综合结构。本文把需求、Prompt、代码、命令、结果和使用边界完整放出来,照着就能复现。
先说一个很常见的场景
写一个 Ready/Valid slice,看上去不算难。
接口只有几根线,核心逻辑也就两个寄存器。熟悉握手协议的工程师,坐下来十几分钟大概就能写完。把同样的需求交给大模型,它可能十几秒就给出一份代码,而且注释还挺多。
麻烦通常从下一步开始。
下游拉低 ready 时,data 有没有保持?缓存已满的那个周期,如果下游正好接走旧数据,上游能不能同时送进新数据?Testbench 最后打印了 PASS,究竟是因为做过逐笔比较,还是因为时间到了就打印?再往后,所谓的"检查通过",指的是文本扫描、Verilog 编译、事件仿真,还是综合?
这些问题都不复杂,但很琐碎。工程里真正花时间的地方,经常就是这种琐碎。
笔者以前也试过直接写一个很长的 Prompt,把接口、复位、编码风格、测试场景全塞进去。结果通常有两种。要么模型顾着功能,忘了项目里的命名和交付格式;要么代码看起来很规整,Testbench 却只覆盖了最顺利的路径。继续追问当然可以修,可几轮对话之后,最初的要求和后来的补丁混在一起,很难说现在这份代码到底遵循了哪套规则。
所以这次笔者换了一个做法:不只给 Codex 一段 Prompt,而是给它装一个专门处理 Verilog 的 Skill。
Skill 到底多做了什么
Codex Skill 可以简单理解为一套放在本地的工作说明。里面不只有提示词,还可以包含规则文件、脚本、模板、检查清单和固定的执行顺序。
这和"换一个更懂 Verilog 的模型"不是一回事。模型还是同一个模型。变化在于,它接到任务以后,不再直接从自然语言跳到 RTL,而是先读规则,再按约定生成中间产物,最后调用真实工具检查。
本次使用的 Erie Verilog Generator v0.4.0,标准生成流程是:
requirements -> codegen_plan -> python -> rtl
requirements 负责把自然语言整理成结构化需求;codegen_plan 先决定模块接口、状态和时序;python 阶段生成一个语义模型,给后面的 RTL 和测试向量提供参照;最后才输出 Verilog。
RTL 生成以后,流程还没有结束。Skill 会继续运行独立静态 Lint、formatter AST 检查、注释与命名检查,以及最终的 Verilog deliverable gate。需要仿真或综合时,再接到 Icarus、Vivado、VCS 或 Yosys 这类外部工具。
普通 Prompt 和 Skill 的区别,可以放在一张表里看:
|
普通 Prompt |
Skill 工作流 |
|
需求直接进入代码生成 |
先整理 requirements,再写 codegen plan |
|
编码习惯主要靠 Prompt 约束 |
读取版本固定的规则和检查清单 |
|
Testbench 可能只有激励和波形 |
要求比较、错误计数、watchdog 和明确退出状态 |
|
模型说"看起来正确" |
脚本和外部 EDA 工具给出返回码 |
|
修改后不容易追踪依据 |
保存规格、计划、生成产物和验证报告 |
|
一次回复就是主要交付物 |
RTL、TB、JSON 报告和终端证据共同组成交付 |
笔者比较在意最后一行。RTL 开发不缺一段"看起来能用"的代码,缺的是代码旁边那套能复查的依据。
安装 Erie Verilog Generator
项目地址是:
https://github.com/Eriemon/verilog-generator
本文固定使用 v0.4.0,对应 commit:
25bca69b33bb2d1252262a3b103646bef0116ae0
固定 tag 很有必要。Skill 里有规则和验证脚本,默认分支以后发生变化,同一条 Prompt 得到的流程也可能不同。写教程时如果只说"使用最新版",过几个月往往就不好复现了。
在 macOS 或 Linux 上,可以把仓库放进 Codex 的 Skill 搜索目录:
git clone --depth 1 --branch v0.4.0 \
https://github.com/Eriemon/verilog-generator.git \
~/.codex/skills/erie-verilog-generator
进入目录检查版本:
cd ~/.codex/skills/erie-verilog-generator
cat VERSION
git rev-parse HEAD
输出应分别对应 v0.4.0 和上面的 commit。重新打开一个 Codex 会话后,可以直接在任务里点名使用 Erie Verilog Generator。点名很重要,它能把"参考这个仓库的写法"和"实际执行这个 Skill"区分开。
这次还要做真实仿真和综合。macOS 用 Homebrew 安装 Icarus Verilog 与 Yosys:
brew install icarus-verilog yosys
检查工具:
iverilog -V
vvp -V
yosys -V
本次机器是 Apple Silicon,系统为 macOS 27.0,Python 3.14.4,Codex CLI 0.145.0。实际调用的模型是 gpt-5.6-sol,Erie Skill 使用 v0.4.0。最终安装的 Icarus Verilog 是 13.0,Yosys 是 0.67。
如果你的机器没有这两个工具,也可以先跑 Skill 自带的静态检查。但文章里要把话说准:静态 Lint 通过,不等于 Testbench 仿真通过;没有运行 Yosys,也不能写成综合通过。
怎么确认 Codex 真的调用了 Skill
这是第一次使用时最容易含糊的地方。
在 Prompt 里贴一个 GitHub 地址,让 Codex "按照这个项目的风格写",不等于调用 Skill。它可能只是读了 README,也可能只从仓库里挑了几条规则。真正调用 Erie Verilog Generator 时,Codex 应该先读取 SKILL.md,选择 regular、deep_review 或 agentic_repair 其中一条工作流,再执行对应脚本。
笔者的检查方法很直接。开始前要求它报告 Skill 名称、版本和实际根目录;生成时要求保存 requirements、codegen_plan 和 Python semantic model;结束时必须出现 deliverable gate 的 JSON。三项都能对上,才算进入了这套工作流。
还可以看输出目录。如果最后只有一个 .v 文件和一段聊天解释,多半没有走完整流程。正常的 regular 生成至少会留下规格、计划、模型、RTL、验证报告和 trace。文件多不代表代码一定对,但没有这些文件,就很难证明规则和门禁执行过。
笔者也会要求它把所有外部命令原样记录下来,包括工作目录和返回码。iverilog 的标准输出为空很正常,关键是编译命令确实执行过,而且返回 0。相比一句"仿真完成",这类记录不漂亮,却更有用。
这次要生成什么
例子选了一个单槽 Ready/Valid 流水寄存器,模块名为 ready_valid_slice。
笔者选它不是因为代码短,而是因为它刚好能测出生成工具有没有认真处理握手边界。纯组合逻辑太容易,复杂总线又会把文章拖进协议细节。一级缓存处在中间位置,接口少,但对反压、数据保持和同周期收发都有明确要求。
完整 Prompt 如下。实际使用时可以原样交给 Codex:
请调用已经安装的 Erie Verilog Generator Skill v0.4.0,
使用 regular 工作流生成模块 ready_valid_slice。
设计要求:
1. 使用可综合 Verilog-2001。
2. 数据宽度由参数 DATA_WIDTH 配置,默认 32 位。
3. 使用单时钟 i_clk。
4. 使用低有效异步复位 i_rstn。
5. 上游接口为 i_data、i_valid、o_ready。
6. 下游接口为 o_data、o_valid、i_ready。
7. 只有 valid 和 ready 同时为 1 时才完成传输。
8. 模块内部使用一级缓存。
9. 当 o_valid=1 且 i_ready=0 时,o_data 和 o_valid 必须保持稳定。
10. 反压解除后,必须先正确输出缓存数据。
11. 支持连续传输,允许同一周期输出旧数据并接收新数据。
12. 不能丢失、重复或打乱数据。
验证要求:
1. 生成自检查 Verilog-2001 Testbench。
2. 覆盖复位、单次传输、连续无反压传输。
3. 覆盖下游持续反压和反压解除。
4. 覆盖同周期输入握手与输出握手。
5. 使用固定种子的随机 valid/ready。
6. 使用 FIFO scoreboard 检查数据不丢失、不重复、不乱序。
7. 检查反压期间 o_data 和 o_valid 稳定。
8. 加入 watchdog,失败时给出明确错误信息和非零错误计数。
执行要求:
1. 保存结构化 requirements、codegen plan 和 Python semantic model。
2. 生成 ready_valid_slice.v 和 ready_valid_slice_tb.v。
3. 运行 Skill 的独立静态 Lint。
4. 运行严格 Verilog deliverable gate。
5. 使用 Icarus Verilog 实际编译并运行 Testbench。
6. 使用 Yosys 执行基础综合检查。
7. 保存所有实际命令、返回码和关键输出。
8. 不得使用模型自笔者评价代替工具结果。
这里有个小习惯值得保留:把"生成什么"和"怎么验证"分开写。大模型很容易把测试要求当成背景说明。明确写出要生成 Testbench、要运行什么命令、要保存什么证据,执行路径会清楚很多。
写硬件 Prompt 时,笔者还会把接口合同放在最前面。模块名、参数名、端口方向、时钟和复位一旦进入别人的例化脚本,后面再改会牵动不少文件。与其让模型先自由发挥,再要求它把 clk 改成 i_clk,不如一开始逐个列清楚。
行为要求尽量写成时钟边界上的可观察条件。比如"支持反压"有点宽,"当 o_valid=1 且 i_ready=0 时,o_data 和 o_valid 保持稳定"就能直接变成检查逻辑。"高吞吐"也不好测,换成"允许同一周期输出旧数据并接收新数据,连续流量不能插入气泡",实现和 Testbench 都知道该看什么。
最后再写禁止项。这里明确要求 Verilog-2001、可综合 RTL,并禁止用模型自评代替工具结果。若工程里不接受 initial、延时或某些综合器扩展,也应该在这一段说。限制写得越具体,后面靠人工猜测的地方越少。
Skill 产出了哪些文件
这次最终交付目录很朴素:
outputs/
├── wechat_verilog_skill_tutorial.md
├── experiment_summary.md
├── example/
│ ├── ready_valid_slice.v
│ ├── ready_valid_slice_tb.v
│ └── README.md
└── evidence/
├── environment.md
├── commands.md
├── validation_summary.md
├── deliverable_gate.json
└── simulation_output.txt
工作流目录里还保存了结构化需求、生成计划、Python 模型、接口审计和 validation report。平时做一个小模块,未必需要把所有 JSON 都交给下游工程师,但在评估生成工具时,这些文件很有用。你能看到需求是怎样被解释的,也能定位某条检查来自哪条规则。
笔者建议至少保留五样东西:最终 RTL、最终 Testbench、可复制的运行命令、完整仿真输出、机器可读的门禁结果。只有 RTL 和一张波形截图,过一段时间通常很难复现。
这些文件不必从头挨个读。笔者通常先开 ready_valid_slice.v 看接口和时序逻辑,再看 Testbench 是否真的做了比较。接下来直接运行 commands.md 里的编译与仿真命令。代码和结果都对得上以后,再去翻 validation_summary.md 和 deliverable_gate.json。
JSON 报告更适合追问题,不适合拿来理解设计。比如门禁报 VG012,可以在 JSON 中看到规则编号、严重级别和目标文件;但要判断该不该改参数名,仍然得回到原始接口要求。脚本能指出冲突,不能替项目做取舍。
environment.md 也别省。Python、Icarus、Yosys 和 Skill 版本不同,解析规则和 warning 都可能变化。教程要能复现,环境信息至少得写到版本号,而不是一句"在 macOS 上测试"。
RTL 里最值得看的几行
最终模块有一个数据寄存器 reg_pipe_data,再用 flag_pipe_valid 表示这个槽位是否装着一笔尚未被下游接收的数据。
组合握手逻辑如下:
assign flag_pipe_load = (~flag_pipe_valid) | i_ready;
assign flag_input_accept = i_valid & flag_pipe_load;
assign o_ready = flag_pipe_load;
assign o_data = reg_pipe_data;
assign o_valid = flag_pipe_valid;
flag_pipe_load 是这里的核心。缓存为空时,flag_pipe_valid 为 0,模块当然可以接收新数据。缓存不为空时,只有下游 i_ready 为 1,旧数据会在当前时钟沿被接走,槽位才允许更新。
这几种情况可以逐项看:
|
当前缓存状态 |
i_ready |
o_ready |
时钟沿上的动作 |
|
空 |
0 或 1 |
1 |
i_valid=1 时接收新数据 |
|
满 |
0 |
0 |
保持原数据和 o_valid |
|
满 |
1 |
1 |
输出旧数据,可同时接收新数据 |
第三行最容易写错。若把 o_ready 简单写成 ~flag_pipe_valid,缓存满时就永远不能在同一个周期接收下一笔数据,中间会多一个气泡。现在的写法把"旧数据正被消费"也算作可装载条件,所以连续流量下可以做到每周期一个事务。
可以拿两笔数据过一遍。
假设缓存里已经有数据 A,o_valid=1,下游暂时把 i_ready 拉低。此时 o_ready=0,上游即便给出数据 B,本模块也不会接收。数据寄存器继续保存 A,输出端保持 A 和有效信号。
下一周期,下游把 i_ready 拉高,上游仍然保持 B 和 i_valid=1。在上升沿到来之前,输出握手交付的是 A;输入握手也在同一周期成立。到了上升沿,寄存器写入 B,有效位继续为 1。再下一个周期,下游看到的就是 B。
这里要分清"沿前可观察的数据"和"沿后寄存器的新值"。如果只盯着赋值语句,很容易误以为 A 会被 B 覆盖。Ready/Valid 的传输条件是在采样沿同时满足 valid 和 ready,A 已经在那个沿完成交付,B 则成为下一笔缓存数据。Testbench 的采样方式也围绕这个顺序来写。
有效位和数据分别放在两个时序块中,每个 always 只维护一个主要目标。这符合 Skill 的 strict 风格,也方便看波形:
always@(posedge i_clk or negedge i_rstn)begin
if(i_rstn == 1'b0)begin
flag_pipe_valid <= 1'b0;
end else if(flag_pipe_load == 1'b1)begin
flag_pipe_valid <= i_valid;
end else begin
flag_pipe_valid <= flag_pipe_valid;
end
end
always@(posedge i_clk or negedge i_rstn)begin
if(i_rstn == 1'b0)begin
reg_pipe_data <= {DATA_WIDTH{1'b0}};
end else if(flag_input_accept == 1'b1)begin
reg_pipe_data <= i_data;
end else begin
reg_pipe_data <= reg_pipe_data;
end
end
当下游反压时,flag_pipe_load 为 0。有效位不变,数据寄存器也不更新,因而 o_valid 和 o_data 都保持。反压解除后,i_ready 拉高,原来缓存的数据先参与输出握手;如果这时上游也有效,新数据会在同一个上升沿写进缓存,下一周期继续输出。
这段逻辑没有什么神秘的。好处在于需求、实现和 Testbench 用的是同一套握手定义,不需要靠读者猜模型当时怎么理解"同周期输入和输出"。
Testbench 不是最后打印一句 PASS
生成 Testbench 时,笔者最关心的是 scoreboard。
每当输入侧发生 i_valid && o_ready,Testbench 就把这一笔 i_data 写入 FIFO 数组。输出侧发生 o_valid && i_ready 时,它取出最早的一笔期望数据比较:
if(sampled_output_accept == 1'b1)begin
if(scoreboard_read >= scoreboard_write)begin
$display("FAIL: duplicate output data=%h cycle=%0d",
sampled_output_data, cycle_index);
error_count = error_count + 1;
end else if(sampled_output_data !== scoreboard[scoreboard_read])begin
$display("FAIL: out-of-order output expected=%h actual=%h cycle=%0d",
scoreboard[scoreboard_read],
sampled_output_data,
cycle_index);
error_count = error_count + 1;
end
scoreboard_read = scoreboard_read + 1;
end
这个结构同时检查三件事。输出多了一笔,会遇到 scoreboard 下溢;数据内容或顺序不对,会在逐笔比较时失败;输出少了一笔,测试结束排空以后,读写指针和事务计数对不上。
反压稳定性单独检查:
if(sampled_stall == 1'b1)begin
stall_count = stall_count + 1;
if((o_valid !== sampled_output_valid) ||
(o_data !== sampled_output_data))begin
$display("FAIL: output changed under backpressure cycle=%0d",
cycle_index);
error_count = error_count + 1;
end
end
测试开始时先拉低 i_rstn,在时钟边沿之外检查异步复位是否已经把输出状态清掉。随后依次发送单笔数据、连续数据和持续反压场景。第 40 个主循环周期以后,固定 LFSR 种子驱动 i_valid 与 i_ready,总主循环为 200 个周期。最后停止输入,把 i_ready 持续拉高,再给 20 个周期排空缓存。
Testbench 还有一个 watchdog。仿真如果因为等待条件或时钟问题卡住,不会一直挂着,它会打印超时并退出。
固定种子的随机测试不等于覆盖所有情况。不过它有一个实际好处:结果可重复。某次修改出错时,输入序列不会跟着变化,波形和日志更容易对照。
Testbench 的采样时刻也值得看一眼。激励在时钟下降沿更新,随后等待 1 ns,记录当前周期是否发生输入或输出握手。到下一个上升沿,DUT 执行非阻塞赋值;再等 1 ns,Testbench 检查寄存器更新后的稳定性。
这种写法故意绕开上升沿上的竞争。若 Testbench 和 DUT 都在 posedge 同时用阻塞赋值更新信号,仿真结果可能依赖进程调度顺序,波形看着一会儿对、一会儿错。把驱动放到下降沿,留半个周期给组合逻辑稳定,读起来也更接近实际接口时序。
scoreboard 在输出比较之后再写入本周期新接收的数据。这样同周期输出 A、输入 B 时,比较对象仍然是旧队首 A,不会提前拿 B 去比。对于只有一级缓存的设计,这个细节很小;换成多级流水或小 FIFO,采样和记分顺序如果不清楚,Testbench 本身就会制造假错误。
在 macOS 上编译和运行
进入交付目录后,先用 Icarus 按 Verilog-2001 编译:
iverilog -g2001 -Wall \
-s ready_valid_slice_tb \
-o ready_valid_slice_tb.out \
ready_valid_slice.v \
ready_valid_slice_tb.v
编译返回码为 0,没有 warning。接着运行:
vvp ready_valid_slice_tb.out
本次真实输出是:
PASS: ready_valid_slice self-check complete input=121 output=121 stalls=25 simultaneous=89
outputs/example/ready_valid_slice_tb.v:197: $finish called at 2235000 (1ps)
这几个数字不是为了凑一行漂亮的日志。input=121 和 output=121 说明输入与输出事务数相同;stalls=25 说明测试确实观察到了有效数据被反压的周期;simultaneous=89 说明同周期输入和输出不是纸面上的测试意图,仿真里真的发生过。
再用 Yosys 做一次基础综合检查:
yosys -p 'read_verilog ready_valid_slice.v; \
hierarchy -check -top ready_valid_slice; \
proc; opt; check; stat'
关键输出如下:
Top module: \ready_valid_slice
Found async reset \i_rstn
Found and reported 0 problems.
8 ports
5 cells
2 $adffe
Yosys 成功识别顶层模块、异步复位和两个带使能的触发器组,check 没有发现结构问题。这里能写"基础综合检查通过",但不能顺手写成"时序通过"。这个命令没有时钟约束、工艺库,也没有布局布线。
Skill 的门禁结果怎么看
外部仿真和综合跑完以后,笔者又保留了 Erie Skill 自己的检查结果:
|
检查 |
实际状态 |
说明 |
|
RTL 独立静态 Lint |
通过 |
0 error,0 warning |
|
Testbench 独立静态 Lint |
通过 |
0 error,0 warning |
|
formatter AST |
通过 |
识别 1 个 RTL module,无解析错误 |
|
Icarus Verilog 编译 |
通过 |
Verilog-2001,返回码 0 |
|
自检查 Testbench |
通过 |
返回码 0,121 笔输入与输出一致 |
|
Yosys 基础综合 |
通过 |
返回码 0,check 为 0 problems |
|
strict deliverable gate |
未通过 |
剩余 1 个 VG012 |
|
时序与实现 |
未运行 |
本实验没有工艺库、约束和实现流程 |
|
CDC/RDC 与形式验证 |
未运行 |
不在这个小实验的验证范围内 |
唯一剩下的 VG012 是参数命名冲突。需求明确要求公共参数名为 DATA_WIDTH,而 Erie 的 erie_strict profile 要求模块参数使用 C_ 前缀,也就是 C_DATA_WIDTH。
笔者保留了 DATA_WIDTH,因为它是实验接口的一部分。于是仿真通过、Yosys 通过,但 strict deliverable gate 仍然是 delivery_ready=false。这并不矛盾。前两个工具检查语法、行为和可综合结构,Skill 门禁还检查自己的命名规则。
如果这是一个允许调整接口的新项目,可以把参数改成 C_DATA_WIDTH,让代码完全服从该 profile。若项目已有模块接口和脚本依赖 DATA_WIDTH,更合理的做法是记录这项偏差,而不是为了门禁全绿悄悄改接口。
笔者反而喜欢这个不太整齐的结果。它提醒使用者,质量门禁不是总分。每一项检查都有自己的对象,读报告时要知道它究竟检查了什么。
实际使用时,笔者会怎么安排
如果把这套 Skill 放进日常 RTL 开发,笔者不会每次都让它从零生成完整模块。
接口明确、逻辑规模不大的模块,比如协议适配壳、寄存器切片、计数器、简单仲裁和控制状态机,适合走完整生成流程。先让 Skill 整理需求,再看 codegen plan。接口或时序理解不对,最好在代码出来以前纠正。
已有 RTL 出问题时,更适合走 existing RTL 的分析或 verify-repair 分支。先让工具读取源代码和日志,输出诊断与修复计划,再决定是否应用修改。不要看到一次仿真报错,就让模型重写整个模块。那种改法很快,review 时也最让人头疼。
Testbench 生成可以单独使用。即便 RTL 是工程师手写的,让 Skill 根据端口和行为要求搭一个带 scoreboard、watchdog 和随机握手的骨架,也能省掉一部分重复劳动。生成完仍然要读一遍,尤其检查采样时刻、复位释放和非阻塞赋值带来的一个周期差异。
还有一条很朴素:命令和返回码要跟代码放在一起。聊天窗口里的"已验证"很容易丢,commands.md 和 simulation_output.txt 不会。团队里另一个人拿到目录,至少知道该从哪条命令开始复跑。
它适合做什么,不适合做什么
Erie Verilog Generator v0.4.0 适合 Verilog-2001 模块生成、现有 RTL 分析、静态检查、自检查 Testbench 骨架和早期综合准备。它对命名、注释、区域划分和单目标 always 块有比较强的意见。如果团队本来就有一套稳定风格,需要先比较两边规则,不要直接混用。
它不是 UVM 环境,也不会替你完成 CDC、RDC、形式验证、STA 和签核。多时钟设计、异步 FIFO、安全相关控制、复杂 AXI 互连,不能因为一个随机 Testbench 跑过就放松验证。Skill 能帮忙整理工作和生成第一版,但验证计划仍然要由工程师负责。
另外,Python semantic model 也只是工作流中的参照模型。它可以帮助 RTL 和测试向量保持一致,却不是数学意义上的规格证明。若 Python 和 RTL 从一开始就共享了同一个错误理解,两边完全一致也可能一起错。
说到底,这个 Skill 最有用的地方不是"会写 Verilog"。直接对话也会写。它把需求、计划、代码和工具结果串成了一条可复查的链路,让工程师能在每个节点插手。
写在最后
这次例子很小,最终 RTL 也没有炫技。可笔者愿意把它当成一个合格的工程样本,因为读者能拿到 Prompt、源代码、Testbench、仿真命令和综合输出,并且能看到那条没有被藏起来的 VG012。
笔者现在会放心让 Codex 加上 Erie Skill 去写这类模块的第一版,也愿意让它先搭 Testbench。但代码合入以前,笔者还是会自己看握手边界、跑仿真、读综合日志。对 RTL 来说,这点麻烦省不了,也不该省。
项目与复现信息:
- 项目:https://github.com/Eriemon/verilog-generator
- 固定版本:v0.4.0
- commit:25bca69b33bb2d1252262a3b103646bef0116ae0
- 示例 RTL:outputs/example/ready_valid_slice.v
- 示例 Testbench:outputs/example/ready_valid_slice_tb.v
- 仿真证据:outputs/evidence/simulation_output.txt
- 门禁结果:outputs/evidence/deliverable_gate.json
下次再看到一句"这段 RTL 已经验证过",笔者大概还是会追问:跑的是哪条命令,返回码是多少,输出存在哪里。Skill 没法替人判断设计能不能签核,但它能把这几个答案留在目录里。对一个小型 RTL 生成工具来说,笔者觉得这已经很实在了。
更多推荐




所有评论(0)