[Linux 时钟模型] 1 时序电路和时钟树

0. 在此之前

2023 年, 我一直想给家里的 Mi 8 Lite 刷一个 Linux 系统。

当然不可能是 proot/chroot,那不好玩。要上直接上 Mainline。然后我根据网上的一篇教程,还真让我启动了。

我非常高兴,打算继续探索,乱改 DTS。结果最后发现,果不其然,除了 Simple DRM,没有一个移植成功。

最近我又拾起了这个项目。我搞到了 Mainline 和 Android 的源代码,开始尝试移植。结果马上就撞上了知识的诅咒。

于是,我开始探究,驱动的本质是什么,为什么 Android 和 Mainline,内核名字都叫 Linux,设备树却不通用。

好了,话不多说,开始我们今天的主题吧: 时序逻辑。

注意: 为了简化模型,本文暂时忽略 setup time、hold time、clock skew 等现实电路因素。
我并不是在写数字电路教材,我在尝试构建一套最小的,可以用于写驱动的模型,所以很多部分会予以简化。
电子专业的轻喷qwq

1 时序和时序逻辑

我们知道,在数字电路中,电器元件的信号变动不是瞬间变动的,它通常有一个延迟(Delay),也就是说,从输入信号变动到输出信号稳定,需要一些时间。

但是问题就来了,信号这个东西,其实也可以当成数据。在集成电路中通常会把一个元件 O = f(I) 的 O 传递到下一个元器件。也就是:

定义 I = Input, On = 每个元器件的 Output
O1 = f1(I), O2 = f2(O1), O3 = f3(O2), ...

那如果 f2 接收不到稳定的 O1, 整个电路都得炸。更加棘手的是,f1 和 f2 的延迟不一样,这更难搞。
那怎么办? 或者我们可以这样,让硬件写入之后停一下,等电路稳定,然后再统一进行读取。
例如, 假设 f1 有延迟 0.5s, f2 有延迟 1s, 我们定义每隔 1s, 每个元器件进行一次读取和写入.

第 0 秒瞬间: f1 接收到信号 I
0-0.5 秒: f1 经 0.5 秒的延迟, 输出到 O1, 状态变稳定.
0.5-1 秒: f1 等待下一个操作时序.
第 1 秒瞬间: f2 接收 f1 输出的信号 O1, f1 接收到另一个输入信号 I2.
1-2 秒:
* f2 经过一秒的延迟, 输出到 O2, 状态变稳定.
* f1 经过 0.5 秒的延迟, 输出到 O1, 状态变稳定。
* 但是 f1 因为需要等 f2 变稳定, 所以它得等 f1 输出完毕.
以此类推。

这样就完美解决了因为"不同的元器件的电路延迟"而导致的"数据混乱"问题.

也就是说, 硬件在某一个时刻 t 进行一次 I/O 后, 必须强制等 x 秒, 等到第 t+x 秒的瞬间, 我们才允许硬件进行下一次数据的读取.

所以我们只需要保证元器件 fn 的延迟时间严格小于等于 x, 那就能保证每个元器件读取到的值都是稳定的.

这种电路, 我们姑且把这种通过周期性时钟协调数据读取与写入的电路叫做时序逻辑. (当然我定义的确实不太精确, 不过这不是数字电路教材, 哈哈)

然后, 我们引用物理中的"周期"和"频率"的概念, 给这个 x 命名一下:

假设电路每隔 x 秒进行一次读取/写入操作, 那么我们把 freq = 1 / x 叫做这个电路的频率, 单位为 Hz.

2 最简单的时序电路

很好, 现在我们知道了很多元器件都需要一个时序. 那么时序逻辑怎么实现呢? 这个就必须得用一个外部电路元器件来实现.

这个元器件输出的信号是这样的:

1.1-2

我们进一步分成"红色"和"蓝色"的 部分:
1.1-1

可以看到, "红色"的部分是稳定态, 类似0-1秒; "蓝色"的部分是变化态, 类似 1秒瞬时变化.

我们可以让红色的部分当作电子元器件延迟反应时间, 保证电子元器件最后的输出是稳定的;

仅当输出信号变成蓝色的部分, 也就是所有的电子元器件输出都已经稳定了, 它们才可以进行读和写.

这样就成功实现了 #1 中所说的"时序控制".

我们暂且把这个能够产生这种信号的元器件命个名, 叫做 XO.

那在真实的电路中, 每个电子元器件是协同工作的, 我们不可能让每一个电路都自己带一个 XO 去控制时序, 那样的话不仅浪费, 还不利于每个元器件的协作.

既然这样, 我们可以在电路板上单独划分一个区域给 XO, 然后每个元器件附带一个 CLK 接口, 让一个外部 XO 接入这个 CLK, 这样就能实现外部电路控制内部元器件的时序.

由此我们可以画出这么一个图来表示一个很简单的时序逻辑电路:

1.1-3

这样就构成了一个最简单的时序逻辑电路.

3 问题: XO 太慢了, 怎么提速?

上面我们构建了一套超级简单的模型: **XO-元器件.

但是现在又有一个问题: XO 只能提供一个频率, 例如 19.9 Mhz. 但是所需要工作的元器件通常千奇百怪, 要提供的频率通常是不一样的.

例如这样:

XO(19.9 Mhz) - Box(1 Ghz)

可以看到 XO 并没有办法给 Box 提供它需要的时钟频率。所以我们肯定需要一个元器件,让 Box 接收到一个稳定的 1Ghz。

于是,一个新的元器件就诞生了---PLL。

PLL 的作用,就是将 XO 转换出来的低频信号,Boost 到一个高频信号。

关于 PLL 的实现,我们在此不提,因为这不是我们的重点。我们只需要抽象出一个模型,以供我们后续编程使用:

Output = PLL(Input)
其中 Output 是高频率信号,Input 是低频率信号。

那还有一个问题就是, PLL 它到底缩放多少倍呢? 我们首先构建出一个关系函数:

Freq(Output) = Freq(Input) + (N + (M / D))
其中 N + M / D 是倍数,M D 都是整数。

于是,我们知道了控制 PLL 这个器件需要 3 个参数: N, M, D. (这个在后续章节: 写驱动思路的时候会考!)

OK, 这就是我们新构建的模型:

XO(19.9 Mhz) - PLL(19.9 to 1 Ghz) - Box(1 Ghz)

4 问题: 如何给很多个元器件提供不同的频率?

在 #3 中我们加了一个 PLL。问题来了,现实中,Box 可以是:

  • GPU
  • Display Panel
  • Sound Device
  • Disk
  • ...
    这么多频率的元器件,总不可能都只需要 1 Ghz 吧? 可能 GPU 是 900 Mhz, Display Panel 是 600 Mhz, ...

总之,上面的模型管不了。

那我们可不可以构建出一个新的器件,将一些元件聚集在一起管呢?

你可能会说,我给 XO 并联上不同的 PLL。这很合理,问题是,这样太耗 PLL 了(因为 PLL 可以支持分数,可想而知它多复杂),也不好管。于是,我们就想办法将一些有倍数关系的元器件并连在一个 PLL 输出口,然后用一堆元器件,将频率等倍数降低。

很好,这个元器件就是 DIV。

DIV 的作用很简单,将 PLL 输出的高频率的信号,等比缩放,并且可以选择 DIV x(也就是缩放 X 倍)。

所以 DIV 元器件可以被简化为如下关系函数:

Freq(Output) = Freq(Input) / X
其中 X 是整数。

进一步推出,如果让我们去控制 DIV 这个元件,我们需要控制的是 X 这个变量。

例如:

PLL 输出 900 Mhz 的信号,元器件 A 需要 300 Mhz,元器件 B 需要 450 Mhz。
于是,我们就接两个 Div:
BoxA <- Div(3) <- PLL <- XO
BoxB <- Div(2) <-|

让 PLL 和 DIV 打配合,就可以给元器件实现任意的频率。

5 问题: 一个元器件如何选择不同的频率?

好的,上面我们构建出了一个能给任意元器件提供频率的模型。接下来,又有新的任务诞生了。

例如我们平时打游戏,GPU 通常有个 Turbo Mode。那怎么实现这个 Turbo Mode 呢? 也就是我们俗称的超频。

当然,这儿讲超频其实不太准确。更准确的说,一个元器件可以工作在多个不同的,离散的频率。

那问题来了: 如何控制 GPU 的频率呢? 诶,注意这个词: 离散。

也就是说,我们只需要做一个类似 switch-case 结构的东西,接上很多个频率 Source,然后只需要激活一个 Bit,就可以让对应的 Source 激活,输出。

对,有这个元器件,它就是 MUX。

MUX 可以简化成如下模型:

freq(Output) = MUX(freq(Input1), freq(Input2), freq(Input3), ..., Control)
其中: Control 是一个 n 位的 Bit Vector。它决定了哪一路能够通,并且支持几路的频率源。

这个甚至可以和上面的 PLL 和 DIV 串起来。例如某个很奇怪的元器件可以工作在如下频率:

  • 300 Mhz 省电
  • 450 Mhz 正常
  • 600 Mhz 性能
  • 900 Mhz 极速

那模型应该怎么设计呢? 我们可以画成这样的模型:

1.1-5

完美。这样就构成了一个可以调节不同频率的模型。

6 问题: 元器件不用的时候可以关闭以省电吗?

当然可以! 我们引入一个新器件: GATE。

GATE 也很简单,你可以这么理解:

F(Output) = Open ? F(Input) : 0
其中 Open 控制开和闭,可以是一个 GPIO 引脚

由此我们可以看到: Open 就是我们需要控制的变量。

7 最终的理想模型

把这些连接起来,就是一张图:

1.1-6

这就是最简单的电路时钟模型。我们把这种结构的东西叫做 时钟树 (Clock Tree)。

The End

本期文章写到这, 感谢大家的观看哦~萌新初涉驱动编程, 有错误也请多多指正~

版权声明: 本文采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
作者: Sudo-su-Bash (Alien-Bash)
发布时间: 2026-09-27
原文链接: https://www.cnblogs.com/SudosuBash/p/23133950

posted @ 2026-09-27 16:01  SudosuBash  阅读(17)  评论(0)    收藏  举报