ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

芯片设计第一课怎么学?从数字电路到Verilog的完整闭环

芯片设计第一课怎么学?从数字电路到Verilog的完整闭环

去年我带了一个零基础转行过来的新人,他坐下来的第一个问题不是"封装是什么",也不是"时序约束怎么敲",而是很认真地看着我说:芯片入门的第一课到底应该学什么。我问他为什么这么问,他说自己看了七八个帖子、四五个视频,已经彻底看晕了。有人让他背 Verilog 语法,有人让他先啃完整本数字电路教材,还有人直接丢给他一块开发板和一份体系结构PDF,仿佛每一条路都是对的,又仿佛每一条路都通向劝退。

这个问题看起来简单,其实问到了芯片行业最尴尬的地方:第一课的教学设计在大多数公司和个人开发者那里,根本没有统一答案。芯片设计这条路,技术栈又宽又深,从RTL编码到验证方法学,从仿真工具到时序收敛,每一块都有人主张"应该先学"。而所谓的"第一课之争",争的根本不是哪个知识点优先级高,而是"当你刚站在这条路的入口时,应该用什么姿势迈出第一步"。

1. 先写代码还是先补数电:带新人时遇到的真实分歧

1.1 两种典型入门路径画像

我在带新人的最初几周里,见过两条特别典型的路线,它们几乎代表了网上两拨互不服气的声音。

第一路是"语法先行派"。这批人觉得芯片设计就是写代码,写了代码就有产出,有产出才有成就感。于是他们花两周把 Verilog 的模块声明、always 块、assign 语句、阻塞赋值和非阻塞赋值全过了一遍,甚至能照着例子写出一个简单的计数器。但问题很快就暴露了:写到状态机时,不知道什么时候该打一拍;遇到组合逻辑和时序逻辑混合的模块,代码画成电路之后完全不是自己想象的样子。最典型的现象是,他们写的代码仿真波形看起来完全正常,放到综合工具里却报出一堆 latch 警告,或者关键路径长到没人敢用。

第二路是"电路根基派"。这批人信奉"数电不牢,地动山摇",先扎扎实实学完数字电子技术基础,门电路、卡诺图、译码器、触发器全都滚瓜烂熟。但等他们终于打开仿真工具准备实战时,又懵了:教科书里画的是逻辑门和晶体管,而RTL文件里是 always 和 assign,两边怎么都对不上。他们会反复问一个问题:我明明知道D触发器应该长什么样,为什么读这一段代码就是想象不出对应的电路?

1.2 为什么两边都没说错,但都不完整

这两条路线的问题,不在于知识点选错了,而在于把"过程中会用到的知识"当成了"起点上必须先啃完的知识"。

芯片设计的第一课,真正要解决的不是"会不会写",甚至不是"懂不懂数电",而是"能不能建立数字硬件的思维模型"。这个思维模型的核心,是眼睛里看到的每一行代码,大脑里都能自动浮现出寄存器、组合逻辑、数据通路的流动方向。当你建立这个模型之后,Verilog语法只是表达工具,数电知识只是设计约束,两者都不需要花几百个小时去单独攻克。

所以我后来给那个新人的回答是:你不需要在第一课里选"先学代码"还是"先学数电",你需要的是在一个完整的小闭环里,同时把代码、电路、仿真、验证这四件事各学一点点。这就像学游泳,你不能先在岸上把换气、蹬腿、划水分别练到满分再下水,而是下水呛几口,再上来改动作,效率反而最高。

2. 把"数字电路基础"拆开看:真正值得先学的是哪几块

很多新手听到"学数电"就害怕,觉得那是整整一本几百页的教材。但其实,芯片入门第一课所需要的数字电路基础,是可以砍到很精简的。我不建议从头到尾啃教材,而是建议只挑几个必选项,把每个必选项都跟RTL代码对照着学。

2.1 入门阶段真正绕不开的三块

我给自己带的新人划定了一个"最小数字电路知识集",包含三块内容。

第一块是组合逻辑。与或非门、多路选择器、译码器、比较器。这一块对应RTL里的 assign 语句和 case 语句,是数字设计里最像"搭积木"的部分,学起来最快。

第二块是时序逻辑。D触发器、寄存器的建立时间和保持时间概念、同步复位和异步复位的区别。这一块直接决定了你写的 always 块到底是在生成寄存器、生成锁存器,还是生成一堆不可综合的怪东西。

第三块是有限状态机。状态编码、状态转移条件、输出是米利型还是摩尔型。这是数字设计里最抽象但也最实用的模型,几乎所有的总线控制器、通信协议处理都离不开它。

我会用一个表格来帮新人自查掌握程度:

知识点最低要求与RTL的对应关系快速验证方法
组合逻辑能写出真值表并化简assign、case、?: 表达式写一个8选1多路选择器并完成仿真
时序逻辑理解时钟边沿采样和复位方式always @(posedge clk)写一个带复位的计数器并观察波形
状态机能手绘状态转移图三段式状态机写法写一个序列检测器并跑通仿真

2.2 建立时间和保持时间:第一课就要种下敬畏

很多新手觉得建立时间和保持时间是后端工程师才需要关心的事,自己写RTL阶段不用管。这个想法我特别想纠正。确实,前端RTL阶段不会让你手工去算建立时间余量,但如果你不理解这两个概念,你就会写出"在时钟边沿同一时刻同时改变数据和使能"这种危险的代码,或者在跨时钟域时随手就打一拍,完全意识不到亚稳态的存在。

我对新人的要求是:不需要会算,但要能解释清楚"为什么信号不能在时钟边沿附近变化"以及"为什么异步信号进同步逻辑前要先同步两级"。这件事花不了两个小时,但带来的意识会影响后面所有代码。

2.3 一个练习案例:边沿检测计数器

有了以上基础之后,我建议新人做的第一个练习不是流水灯,而是一个带边沿检测的计数器,因为它同时用到了组合逻辑、时序逻辑和一点状态逻辑。

module edge_counter ( input wire clk, input wire rst_n, input wire sig_in, output reg [15:0] cnt ); reg sig_d1; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin sig_d1 <= 1'b0; cnt <= 16'd0; end else begin sig_d1 <= sig_in; if (sig_in && !sig_d1) cnt <= cnt + 1'b1; end end endmodule

这个模块里最值得讲明白的就是sig_d1 <= sig_in这一句。它把输入打了一拍,再用sig_in && !sig_d1检测上升沿。新手如果能自己画出这个电路图中寄存器的位置,并说清楚为什么不能直接用sig_in做边沿检测,那第一课的电路思维就算初步建立了。

3. HDL语法完全可以从项目中学,但顺序要严格

3.1 不要背语法,要让任务驱动学习

很多人学Verilog的方式是拿着一本语法手册从头翻到尾。我特别不建议这样做。语法手册是字典,不是教材。字典是用来查的,不是用来背的。芯片设计里的HDL语言,本质上是在描述电路,你背下来的语法知识如果没有对应的电路概念,最后只会停留在"会抄,不会改"的层次。

我的建议是:只学以下这几个语法主题,就可以开始写代码了。模块端口声明、wire和reg的区别、always块与assign语句、阻塞赋值与非阻塞赋值、parameter和localparam、简单的testbench写法。至于generate、task、function、宏定义这些,等真正遇到需求时再查,查一次就会了,那种记忆远比通读教材深刻。

3.2 一个最小可跑的仿真闭环

我给新人布置的第一次仿真作业是:写一个4位计数器,计数到15自动归零,再写一个testbench验证功能。这个作业麻雀虽小,五脏俱全。

计数器模块:

module counter_4bit ( input wire clk, input wire rst_n, output reg [3:0] count ); always @(posedge clk or negedge rst_n) begin if (!rst_n) count <= 4'd0; else count <= count + 1'b1; end endmodule

testbench:

module tb_counter_4bit; reg clk; reg rst_n; wire [3:0] count; counter_4bit u_counter ( .clk (clk), .rst_n (rst_n), .count (count) ); initial begin clk = 1'b0; rst_n = 1'b0; #100; rst_n = 1'b1; #1000; $finish; end always #10 clk = ~clk; initial begin $monitor("At time %t, count = %d", $time, count); end endmodule

跑完仿真之后,我会要求新人回答三个问题。第一,为什么复位要拉低100个时间单位后才释放?第二,#10这个延时产生的时钟周期是多少?第三,如果去掉复位释放后的等待,直接复位会不会有问题?这三个问题虽然简单,但能逼着新人把"初始状态""时间推进""波形时序"这三个概念对齐。

3.3 综合器视角:语法是思维的映射

有一件事我会在第一课反复强调:你写的每一行Verilog,最终都会变成实际的硬件电路。综合器不会因为你写了always @(*)就把代码"智能地"变成你想要的东西,它只是把你描述的行为翻译成门级网表。很多新手写出一段代码后从不停下来问自己:这段代码综合出来是什么样子?是真值表?是触发器组?还是锁存器?

有个特别经典的例子:在组合逻辑的 always 块里少写了某个分支的赋值,综合器就会生成一个锁存器。新手看到仿真波形正常,就觉得很奇怪,为什么综合报告里有 latch。这时候我会让他画一下电路图,他画着画着就明白了:case 分支没写全,综合器为了保证未列出的情况保持原值,只能引入一个可存储状态的单元。这个教训比任何语法书都直观。所以,请一定养成习惯:写完代码后先自己脑补一遍电路结构,再去动仿真器。

4. 工具链要不要第一课就装:环境铺垫与劝退点

4.1 先从仿真入手,综合工具可以晚一点

芯片设计的工具链比普通软件开发要重得多,尤其是商用综合工具和布局布线工具,从安装到许可再到项目配置,每一个环节都可能劝退新人。所以我的建议是:第一课只需要搭一个能跑 Verilog 仿真的环境,综合和时序分析放到第二课或第三课再说。

原因很简单。仿真工具解决的是"功能对不对"的问题,这在入门阶段最重要。综合工具解决的是"能不能落成电路、性能好不好"的问题,对于刚开始接触RTL的新人来说,先不必急着面对。先编译通过,先仿真通过,先把验证的思维方式建立起来,再引入综合,会平滑很多。

4.2 环境搭建中最容易劝退新人的三个坑

我见过太多人卡在环境搭建这一步,一卡就是一两个星期,最后人直接跑了。这里我总结几个最典型的坑。

第一个坑是"上来就装重型IDE"。很多新手习惯学软件开发时用IDE的思路,觉得写Verilog也应该有漂亮的项目管理器、代码补全、波形窗口一键打开。但这类工具下载包大、配置项多,很容易把人的耐心耗尽。我个人的建议是:初期只需要一个文本编辑器、一个开源的Verilog仿真工具、一个波形查看工具,三条命令行就能跑完编译、仿真、看波形的闭环。

第二个坑是"想用商业级综合工具做练习"。商业综合工具确实功能强大,但它的设计目标是大规模、复杂工艺的项目,不是让新手跑一个计数器。拿它做练习,你会被一大堆工艺库文件、约束文件和脚本配置淹没,练不了几行代码,光折腾环境了。入门阶段完全可以用另一套轻量级的工具流程,先把仿真和基础综合跑通。

第三个坑是"忽视命令行,只依赖图形界面"。图形界面点按钮当然直观,但脚本驱动的工程管理能力,是从第一课就养成的职业习惯。等你的模块多起来,几十个文件编译一遍,每回都在GUI里点点点,效率会非常低。建议从第一次跑仿真就开始写简单的 Makefile 或用脚本一键编译运行,把命令行当成吃饭的家伙。

4.3 第一课应该养成的仿真工作流

我实际操作中会让新人按下面这个固定流程走一遍,直到形成肌肉记忆。第一步,写完RTL代码和testbench。第二步,用脚本编译这两个文件,生成仿真可执行文件。第三步,跑仿真并产生日志和波形文件。第四步,打开波形查看工具,对照日志看信号变化,确认功能和自己的设计预期一致。

这套流程里最容易被跳过的就是第四步。很多人看到$monitor打印出的数字对了,就觉得自己完成验证了。这远远不够。打印日志只能告诉你"结果对不对",而波形能告诉你"信号之间的关系对不对",比如数据是否在时钟沿之后稳定输出,复位释放后的第一个有效时钟沿行为是否正常。芯片设计的很多bug,恰恰不是结果错了,而是时序关系错了。

5. 从APB总线协议入手,把"孤立模块"变成"系统"

5.1 为什么协议比语法更接近芯片工程师的日常

等新人做完计数器、序列检测器这些孤立的小模块之后,我会立刻带他进入下一个层级:总线协议。因为芯片设计里很少有模块是独立存在的,每个人写的东西都要跟别人写的模块通过接口协议进行通信。APB协议就是其中最友好的一个入门样例,它的信号少,时序规则简单,但已经包含了读操作、写操作、等待状态、外设选择这几个完整的交互概念。

APB的状态本质上是三个状态在跳转:IDLE空闲态、SETUP建立态、ACCESS访问态。这个状态机逻辑简单,但第一次接触总线的同学看到读写使能和地址译码还是会发懵。我的建议是先不看协议里的每个字的文字描述,先把状态转移图画出来,再对照代码一行行读。

5.2 APB Slave 状态流转的代码演练

以下是一个精简版APB从设备示例,里面包含两个寄存器,一个控制寄存器和一个状态寄存器。我把读写的部分都放到了ACCESS状态下完成,以便对应PENABLE有效时的数据传输阶段。这个代码刻意简化了地址译码和位宽参数,但足够用来讲清楚总线交互的概念。

module apb_slave #( parameter ADDR_WIDTH = 8, parameter DATA_WIDTH = 32 )( input wire pclk, input wire preset_n, input wire psel, input wire penable, input wire pwrite, input wire [ADDR_WIDTH-1:0] paddr, input wire [DATA_WIDTH-1:0] pwdata, output reg [DATA_WIDTH-1:0] prdata ); localparam IDLE = 2'd0; localparam SETUP = 2'd1; localparam ACCESS = 2'd2; reg [1:0] state, next_state; reg [DATA_WIDTH-1:0] ctrl_reg; reg [DATA_WIDTH-1:0] status_reg; always @(posedge pclk or negedge preset_n) begin if (!preset_n) state <= IDLE; else state <= next_state; end always @(*) begin next_state = state; case (state) IDLE: begin if (psel) next_state = SETUP; end SETUP: begin if (psel && penable) next_state = ACCESS; else if (!psel) next_state = IDLE; end ACCESS: begin next_state = IDLE; end default: next_state = IDLE; endcase end always @(posedge pclk or negedge preset_n) begin if (!preset_n) begin ctrl_reg <= {DATA_WIDTH{1'b0}}; status_reg <= {DATA_WIDTH{1'b0}}; end else if ((state == ACCESS) && psel && penable && pwrite) begin case (paddr) 8'h00: ctrl_reg <= pwdata; 8'h04: status_reg <= pwdata; default: ; endcase end end always @(posedge pclk or negedge preset_n) begin if (!preset_n) prdata <= {DATA_WIDTH{1'b0}}; else if ((state == ACCESS) && psel && penable && !pwrite) begin case (paddr) 8'h00: prdata <= ctrl_reg; 8'h04: prdata <= status_reg; default: prdata <= {DATA_WIDTH{1'b1}}; endcase end end endmodule

这段代码里我着重要求新人讲清楚的是下一状态逻辑里的SETUP分支。为什么当psel为高且penable为高时才进入ACCESS?因为psel高说明总线选中了本设备,penable高说明访问进入数据阶段。这就是APB协议中两个使能信号配合的精髓。很多新手只盯着always块的语法看,忽略了协议信号之间的时序关系,所以我会用提问逼他注意到这一点。

5.3 验证和覆盖率意识:别绕开,真的绕不开

用APB做第一课还有一个好处,它天然适合引入"验证工程师视角"。当新人写完APB从设备之后,我会要求他写一个简单的测试平台,模拟主设备发起写控制寄存器、写状态寄存器、读控制寄存器、读状态寄存器这几组操作。然后我会继续问:你测试了IDLE到SETUP的特殊情况吗?你测试过总线在SETUP阶段就撤销psel的情况吗?如果你没测,你怎么知道状态机一定能回到IDLE?

这些问题背后就是一个词:覆盖率。芯片设计里,功能覆盖率和代码覆盖率决定了验证的可信度。第一课不需要引入复杂的UVM,只需要让新人意识到一件事:仿真能证明测过的场景是对的,不能证明没测过的场景是对的。写代码只是第一步,能证明它正确才是完整闭环。

6. 我最终给新人划的"第一课"边界(含避坑清单)

6.1 可以跳过和不要跳过的清单

带过几轮新人之后,我心里基本上有一个稳定的第一课边界。这不仅是知识的边界,也是精力的边界。

可以先跳过的东西包括:全套数字电子技术教材的深入学习、复杂的时序约束、工艺库与门级仿真、形式化验证、UVM整套方法论、芯片后端布局布线的任何细节。这些内容不是不重要,而是第一课根本消化不了。很多立志入行的同学总是担心"现在不学后面就来不及",实际上芯片设计是马拉松,第一课贪多,很容易在第二个月就彻底放弃。

而绝对不要跳过的东西,我列成一张清单:

  • 阻塞赋值和非阻塞赋值的区别,以及各自的综合结果
  • 同步复位与异步复位的处理方式
  • 组合逻辑中不完整分支导致latch的原理
  • 时钟沿采样建立时间和保持时间的基础概念
  • 一个完整的仿真闭环:RTL、testbench、编译、波形查看
  • 一个标准协议从状态转移图到代码的完整过程

这六件事如果都能做到"既能解释原理、又能动手实现",第一课就已经合格了。

6.2 判断自己是否学完"第一课"的几个自测题

我给新人准备了一套自测题,不考死记硬背,全部是动手检验。第一题,请写一个模10计数器,带异步复位和使能信号,并用testbench跑通仿真,画出复位释放后的波形注释。第二题,请用一个三段式状态机实现"1011"序列检测器,说明为什么用三段式比一段式更清晰。第三题,请解释下面这段代码的问题在哪里。

always @(*) begin if (en) q = d; end

第四题,请给一个APB从设备添加一个新的32位寄存器,并在testbench里完成一次完整的写读回环验证。第五题,请口头说明你为什么要在异步信号进入同步逻辑前,先经过两级触发器同步。这五题如果都能顺畅完成,我就可以放心地让他去看更深的体系结构、总线协议或者验证方法学。如果哪一题卡住了,说明对应的基础环节还没真正建立起来,宁可回头补,也不要硬着头皮往上冲。

最后再分享一个我个人的体会:芯片学习中最贵的不是课程和工具,而是你花了三个月才发现自己的入门路径选错了。"第一课之争"存在的真正原因,是大多数人把原本应该是一个完整闭环的事情拆成了互相对立的板块。其实数字电路、HDL、总线协议和验证方法,从来不是先后关系,而是同一个设计循环的不同侧面。你能在第一次跑仿真时就用波形看到信号和时钟沿怎么对齐,比你记住一百条语法规则都值钱。第一课结束了,后面的路自然会越走越顺。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进