BLOG

Record, summarize, and improve.

SystemVerilog

1. 组合逻辑(Combinational Logic)—— “即时反应”

组合逻辑就像是一个极其听话、没有任何记忆力的水阀开关

  • 特点: 输出仅仅取决于当前的输入。输入变了,输出立刻跟着变。它记不住一秒钟之前发生过什么。
  • 生活例子: 你家卫生间的声控灯。声音响了(输入=1),灯就亮(输出=1);声音没了(输入=0),灯立刻灭(输出=0)。它不会记住“刚刚有人走过去”。
💡 在电路里长什么样?

最基础的“逻辑门”就是组合逻辑。比如 与门(AND):只有当两个输入同时为 1 时,输出才是 1。

  • 只要有一个输入断开了,输出瞬间变成 0。

2. 时序逻辑(Sequential Logic)—— “拥有记忆”

如果世界上只有组合逻辑,电脑是无法工作的,因为电脑需要存数据(也就是记忆力)。时序逻辑就是负责留住数据的。

  • 特点: 输出不仅取决于当前的输入,还取决于“过去的状态”。
  • 生活例子: 手机的电源开关键。你按一下,手机开机(记住了开机状态);你再按一下,手机关机。它必须“记住”它之前是开着还是关着,才能做出正确的反应。
💡 电路里怎么实现记忆?—— 寄存器(Register)

电路里用来保存 0 和 1 的最小单位叫触发器/寄存器。你可以把它想象成一个带锁的小抽屉:

  • 锁打开时,你可以把数据放进去。
  • 锁关上时,外面发生天大的变化,抽屉里的数据都保持不变

3. 时钟(Clock)—— 整个电路的心跳

既然时序逻辑需要“锁抽屉”,那谁来决定什么时候开锁、什么时候关锁呢?这就是时钟信号(Clock)

时钟是芯片里最核心的信号,它是一个周期性在 01 之间跳变的高频方波,就像人类的心跳一样。

  • 上升沿(Posedge): 信号从 0 跳变到 1 的那一瞬间。
  • 芯片的工作节奏: 芯片里所有的“小抽屉”(寄存器),全都是在时钟心跳的“上升沿”那一瞬间,整齐划一地开锁,把数据存进去,然后立刻关锁。

有了时钟,原本混乱、各跑各的的电子流动,就变成了一个步调一致的“正规军”。

4. 终极画面:它们是怎么协同工作的?

在 CPU(比如 CVA6)里面,任何功能的实现,都是组合逻辑时序逻辑交替配合的。

我们可以用“接力赛跑”来想象这个画面:

  1. 起点(寄存器 A): 时钟心跳了一下(上升沿到了),寄存器 A 把手里的数据放了出来。
  2. 途中(组合逻辑): 数据像水流一样,流过一堆复杂的逻辑门(比如做加法、做比较)。这个流动需要时间。
  3. 终点(寄存器 B): 在时钟的下一次心跳到来之前,数据必须流到寄存器 B 的门口等着。
  4. 下一跳: 时钟再次心跳,寄存器 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$ 种状态(从 00001111,也就是十进制的 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 元】。触发组合逻辑:掉出一瓶可乐,然后状态自动跳回【等待投币】。

第三步:你现在可以尝试的“通关路线”

既然你目前不需要立刻去写成千上万行的工程,我建议你采取“读代码”而非“闭门造车”的策略:

  1. 去在线平台围观: 打开 EDA Playground (edaplayground.com),在里面搜索 Counter(计数器)或 Mux(选择器)的简单 SV 例子。不用自己写,点一下 Run,看看输出的波形,对照着代码找找“心跳时抽屉变色”的感觉。
  2. 尝试人肉“译码”: 找一段几十行的简单 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(一看就知道是空闲状态)。
💡 给你的记忆路线图

你不需要单独去背诵这些关键字。当你接下来看代码时,可以把这个列表当作“字典”:

  1. 看到 always_ff @(posedge clk) $\rightarrow$ 知道这里有一排抽屉在等心跳。
  2. 看到内部用的是 <= $\rightarrow$ 确认是时序赋值
  3. 看到 unique case $\rightarrow$ 知道这是一个多路并行选择器

当你能把这些关键字在脑海中自动翻译成“抽屉、水管、分叉路口”时,SV 的语法结构就已经难不倒你了。