1. 组合逻辑(Combinational Logic)—— “即时反应”
组合逻辑就像是一个极其听话、没有任何记忆力的水阀开关。
- 特点: 输出仅仅取决于当前的输入。输入变了,输出立刻跟着变。它记不住一秒钟之前发生过什么。
- 生活例子: 你家卫生间的声控灯。声音响了(输入=1),灯就亮(输出=1);声音没了(输入=0),灯立刻灭(输出=0)。它不会记住“刚刚有人走过去”。
💡 在电路里长什么样?
最基础的“逻辑门”就是组合逻辑。比如 与门(AND):只有当两个输入同时为 1 时,输出才是 1。
- 只要有一个输入断开了,输出瞬间变成 0。
2. 时序逻辑(Sequential Logic)—— “拥有记忆”
如果世界上只有组合逻辑,电脑是无法工作的,因为电脑需要存数据(也就是记忆力)。时序逻辑就是负责留住数据的。
- 特点: 输出不仅取决于当前的输入,还取决于“过去的状态”。
- 生活例子: 手机的电源开关键。你按一下,手机开机(记住了开机状态);你再按一下,手机关机。它必须“记住”它之前是开着还是关着,才能做出正确的反应。
💡 电路里怎么实现记忆?—— 寄存器(Register)
电路里用来保存 0 和 1 的最小单位叫触发器/寄存器。你可以把它想象成一个带锁的小抽屉:
- 锁打开时,你可以把数据放进去。
- 锁关上时,外面发生天大的变化,抽屉里的数据都保持不变。
3. 时钟(Clock)—— 整个电路的心跳
既然时序逻辑需要“锁抽屉”,那谁来决定什么时候开锁、什么时候关锁呢?这就是时钟信号(Clock)。
时钟是芯片里最核心的信号,它是一个周期性在 0 和 1 之间跳变的高频方波,就像人类的心跳一样。
- 上升沿(Posedge): 信号从
0跳变到1的那一瞬间。 - 芯片的工作节奏: 芯片里所有的“小抽屉”(寄存器),全都是在时钟心跳的“上升沿”那一瞬间,整齐划一地开锁,把数据存进去,然后立刻关锁。
有了时钟,原本混乱、各跑各的的电子流动,就变成了一个步调一致的“正规军”。
4. 终极画面:它们是怎么协同工作的?
在 CPU(比如 CVA6)里面,任何功能的实现,都是组合逻辑和时序逻辑交替配合的。
我们可以用“接力赛跑”来想象这个画面:
- 起点(寄存器 A): 时钟心跳了一下(上升沿到了),寄存器 A 把手里的数据放了出来。
- 途中(组合逻辑): 数据像水流一样,流过一堆复杂的逻辑门(比如做加法、做比较)。这个流动需要时间。
- 终点(寄存器 B): 在时钟的下一次心跳到来之前,数据必须流到寄存器 B 的门口等着。
- 下一跳: 时钟再次心跳,寄存器 B 开门,把稳稳当当的数据存进去。
⚠️ 如果你的“组合逻辑”写得太复杂,水流得太慢,在时钟下一次心跳到来时还没流到寄存器 B,这就叫**“违例”(Timing Violation)**,芯片就死机或算错数了。这就是为什么手机超频太厉害会不稳定的原因。
5. 现在,再看一眼 SystemVerilog
有了上面的画面,你再看这两段代码,就绝对不会懵了:
组合逻辑(声控灯,没时钟,即时变)
代码段
always_comb begin
light = sound; // 只要声音(sound)一变,灯(light)立刻变
end 时序逻辑(小抽屉,看时钟,心跳时才变)
代码段
// @(posedge clk) 意思就是:只在时钟(clk)上升沿的那一瞬间看一眼
always_ff @(posedge clk) begin
q <= d; // 只有心跳瞬间,d 的值才能存进小抽屉 q 里面
end 这就是数字电路最底层的秘密。有了这个大框架,你再去看各种眼花缭乱的芯片架构,本质上都不过是在“算数据”和“存数据”。
太棒了!你能建立起组合逻辑(水流)和时序逻辑(小抽屉)的画面感,数字电路最核心的“任督二脉”就已经打通了。
接下来,我们要把这些零散的元件拼装成一个能干活的“小机器”。0基础快速上手的最佳路径是“用拼积木的方式,做三个经典小玩具”。
不用担心,我们不写复杂的代码,只搞懂它们的原理。
第一步:认识数字世界的语言 —— 0 和 1 怎么表示万物?
既然电路里只有低电压(0)和高电压(1),那我们怎么表示大数字呢?
- 拼宽度(位宽): 一个抽屉(1位,1 bit)只能存 0 或 1。如果把 4 个抽屉并排放在一起,它就能表示 $2^4 = 16$ 种状态(从
0000到1111,也就是十进制的 0 到 15)。 - SystemVerilog 表达法:
logic [3:0] my_data;意思就是“并排的 4 个小抽屉”。
第二步:动手“拼”三个核心积木
在真正的芯片(包括 CVA6)里,不管代码多复杂,90% 的电路都是由以下三种积木拼出来的:
积木 1:选择器(Mux - Multiplexer)—— 电路的分叉路口
- 干啥的: 就像火车的轨道切换器。有几路数据流过来,由一个“控制信号”决定让哪一路通过。
- 生活例子: 电视遥控器切台。输入有 HDMI1、HDMI2、有线电视,你按按钮(控制信号),屏幕(输出)就显示对应的画面。
- SV 核心语法:
case语句。代码段always_comb begin case (sel) 1'b0: out = data_a; // 如果 sel 是 0,让 data_a 过去 1'b1: out = data_b; // 如果 sel 是 1,让 data_b 过去 endcase end
积木 2:计数器(Counter)—— 芯片里的“时间沙漏”
- 干啥的: 每一个时钟心跳一下,手里的数字就加 1。
- 为什么要它: 芯片没有“等5秒钟”的概念,它只能靠数心跳。比如时钟 1 秒钟跳 100 次,计数器数到 500 次,芯片就知道“哦,5秒钟到了”。
- 组合+时序的完美结合: * 组合逻辑负责:把当前的数加 1(
next_count = count + 1)。- 时序逻辑负责:心跳到来时,把新数字存进抽屉(
count <= next_count)。
- 时序逻辑负责:心跳到来时,把新数字存进抽屉(
积木 3:状态机(FSM - Finite State Machine)—— 芯片的“大脑指挥官”
这是数字电路里最神奇、最核心的东西。CPU 能够执行不同的指令,全靠状态机在后台指挥。
- 干啥的: 让电路根据当前的情况,在不同的“状态”之间切换,并发出控制信号。
- 经典生活例子:自动贩卖机
- 状态 A:【等待投币】。如果投入 1 元,切换到状态 B。
- 状态 B:【已投 1 元】。如果再投入 1 元,切换到状态 C。
- 状态 C:【已投 2 元】。触发组合逻辑:掉出一瓶可乐,然后状态自动跳回【等待投币】。
第三步:你现在可以尝试的“通关路线”
既然你目前不需要立刻去写成千上万行的工程,我建议你采取“读代码”而非“闭门造车”的策略:
- 去在线平台围观: 打开 EDA Playground (edaplayground.com),在里面搜索
Counter(计数器)或Mux(选择器)的简单 SV 例子。不用自己写,点一下Run,看看输出的波形,对照着代码找找“心跳时抽屉变色”的感觉。 - 尝试人肉“译码”: 找一段几十行的简单 SV 模块(比如一个简易的加法计算器)。试着用我们学过的概念去拆解它:哪几行是在做即时计算(always_comb)?哪几行是在等心跳存数据(always_ff)?
当你能看懂一个由状态机控制着计数器和选择器的小电路时,你就已经具备了跨入芯片设计大门的常识基础。
对于这三个积木(选择器、计数器、状态机),哪一个的运作逻辑让你最感兴趣,或者觉得还有点模糊?
在 SystemVerilog (SV) 中,虽然它的官方规范里有几百个关键字,但作为非验证方向(或者希望快速看懂开源 CPU 源码如 CVA6)的初学者,你完全不需要全部背下来。
芯片设计和阅读源码时,最核心、出现频率最高的关键字和结构可以分为以下四大类。掌握了这几十个词,你就拿到了通往数字芯片世界的“通票”。
一、 核心基础:定义与数据类型
在看 CVA6 等源码时,你会大量看到这些字眼,它们用来定义电路的接口和数据宽度。
-
module/endmodule:定义一个“模块”(即电路的一个功能盒子)。 -
input/output/inout:定义盒子的管脚方向(输入、输出、双向)。 -
logic:(最重要) 几乎可以代表一切信号(无论是水流还是小抽屉)。 -
bit:双值逻辑(只能是 0 或 1,通常用于仿真测试,不像 logic 还有未知态 X 和高阻态 Z)。 -
parameter/localparam:定义“参数”(常量)。比如定义数据总线宽度是 32 位还是 64 位。CPU 源码里经常用它来实现可配置性。
二、 行为控制:组合与时序(核心积木)
这就是我们之前讲的“水流”和“锁抽屉”的控制命令。
-
always_comb:明确告诉编译器,下面这一段是组合逻辑(水流)。 -
always_ff:明确告诉编译器,下面这一段是时序逻辑(小抽屉)。 -
posedge/negedge:时钟的上升沿(0跳到1的那一瞬)和下降沿。通常连在always_ff @(posedge clk)后面。 -
assign:连续赋值。用于最简单的组合逻辑。例如assign sum = a + b;(一句话实现加法水阀)。
⚠️ 必须掌握的两个赋值符号:
-
=(阻塞赋值):用在always_comb里。意思是代码从上到下执行,前面的算完了后面才能算(像 C 语言)。 -
<=(非阻塞赋值):用在always_ff里。意思是在时钟心跳的那一刻,所有抽屉同时赋值,没有前后顺序。
三、 条件与选择结构:电路的分叉路口
CPU 内部有大量的译码器、选择器,全靠这些结构来实现。
-
if/else:跟高级语言一样。但在电路里,它通常会被翻译成“带优先级的选择器”。 -
case/endcase/default:多路选择器。 -
unique case:SystemVerilog 特有。告诉编译器:“我的这些选择条件绝不会同时发生”。编译器看到这个词,会把电路优化得非常精简、跑得更快。
四、 高级结构:让代码变整洁的“打包工具”
在大型 CPU 项目(如 CVA6)中,为了不让连线乱成一团,会用到 SV 的高级打包语法:
1. interface / endinterface(接口)
- 概念:把一堆有关联的信号打包成一根大电缆。
- 生活例子:以前的显示器线有红绿蓝十几根针脚,现在一根 HDMI 线全部搞定。
interface就是芯片里的 HDMI 线。
2. struct(结构体)与 enum(枚举)
-
struct:把不同类型的变量打包在一起。比如把“指令类型、源寄存器、目的寄存器”打包成一个Instruction结构体。 -
enum:给枯燥的 0 和 1 取个名字。- 不用枚举:
state == 2'b00(谁知道 00 是什么意思?) - 使用枚举:
state == IDLE(一看就知道是空闲状态)。
- 不用枚举:
💡 给你的记忆路线图
你不需要单独去背诵这些关键字。当你接下来看代码时,可以把这个列表当作“字典”:
- 看到
always_ff @(posedge clk)$\rightarrow$ 知道这里有一排抽屉在等心跳。 - 看到内部用的是
<=$\rightarrow$ 确认是时序赋值。 - 看到
unique case$\rightarrow$ 知道这是一个多路并行选择器。
当你能把这些关键字在脑海中自动翻译成“抽屉、水管、分叉路口”时,SV 的语法结构就已经难不倒你了。