仓库源文

.. Kenneth Lee 版权所有 2021

:Authors: Kenneth Lee :Version: 1.1

关于概念空间,接口的一些具体讨论


本文没有什么新的总结,只是根据最近的一些工作,记录一些细节讨论,增强已经总结的 一些概念。

概念空间

在这之前,我想再解释一下什么是抽象和名称空间。比如说,我在你身上挂一个传感器, 连续记录你的垂直运动位置。这样记录一年,我就可以拿到一个很详细的波形图。现在的 问题是:这是否在描述你?

在一般的理解意义上,这当然是在描述你。因为按我们一般人的理解,你就是这个波形图 的原因,因为你主动的运动,所以我们才有了这幅图。但为什么你某天突然一下子在一分 钟内垂直升高100米?这个原因不见得就是你,这个原因可能是电梯。

这里我们想强调的是,我们看待一个事情,永远都是看到它的一个呈现,这个呈现的原因 ,也永远都是基于综合的作用的,所以描述什么,以及描述什么的原因,只是一个度的问 题。

现在我们再在你身上装另一个传感器,记录你的水平运动轨迹,记录一年,我们又有了一 个图,这个图同样也是在描述你。那么,到底是水平传感器的记录是你,还是说垂直传感 器的记录是你呢?如果我们再加上温度传感器,血压传感器,不同地方血管的血压传感器 ,你身边十厘米处风速传感器,你10米内人数传感器,你五米内动物的数量传感器,你所 在的城市的传感器……等等等等,这些是否也在描述你呢?

好了,通过这个例子,我们至少认清楚了一点:我们描述任何人的某个方面,都是描述这 个人,但永远都不是在描述这个人的全部。当我们的思路被拉到某组相关数据去的时候, 我们的逻辑就全部都在这些数据上,我们丢弃了其他逻辑,但也正因为我们丢弃了其他逻 辑,所以我们才看清了某个“角度”的逻辑。这个单独讨论某个角度的数据的名称的因果关 系描述,就是我们说的“概念空间”,或者“逻辑视图”,或者简单说,“视图”,“View”。

视图,只是我们寻找相关性的一种方法,而且,在我的认识中,它可能是唯一的方法。但 它的缺陷也非常明显,就是它不一定表示全部信息,也不一定是最好的相关性。你用概念 视图,开发视图,运行视图去描述一个系统,它不见得就能最好地控制这个系统的发展。

名字,你可以无限定义,都有可能说得头头是道,但离开了对现实的控制,它只能变成“艺 术”。在我们看电影的时候,很多时候一些电影情节逻辑上是说不通的,但我们仍被其他细 节所吸引,我们仍把它看做是真实去看待它。这是因为电影本身,完全是人脑中的存在, 是纯粹的“名称空间”,它没有逻辑性是可以的(当然,不能太过分,否则它会被人脑对逻 辑的天然美好追求所抛弃)。

但架构设计不行,因为架构设计的目标是落地,它建的逻辑,无论抽象到多高,最终都是 要被实施的。

细节逻辑对接口的反向限制

这就是这里要说的第二个问题:每层抽象都是被下层的逻辑直接影响的。比如说你做一个 芯片吧,你会选择一种指令集,比如加法叫add,乘法叫mul。你换一个指令集,加法叫 great_add,乘法great_mul,这其实关系不大,这仅仅是一个名字的改变。这在逻辑空间 中是最轻度的变化。但如果这两个算法的内涵是不同的,比如,第一个add的定义是这样的 :::

    add rs1, rs2, rd
            add rs1, rs2 and flag.carried to rd

而第二个great_add的定义是这样的:::

    great_add condition, rs1, rs2, [rd]
            if confition flag equal to global flags.c, add rs1 and rs2 and
            place the result in the 8 byte memory poited by rd in flags.e
            endian format.

这就定义了两个很不一样的名称空间。

请认真思考一下这两个例子。两个指令都为芯片的实现提供了一个高层约束,我不管你用 电子管,还是用晶体管,甚至用量子计算那些复杂的概率波理论来实现这个约束。你会有 你细节的约束,但你只要要实现这个接口,这个接口就约束了你必须有构架寄存器 r1...rN,要有flags.carried。我也不管你这些rX到底表达为RegFile的一个rename,还是 表现为概率云的某个编码中的一段,你内在的逻辑中就必须保证我给你两个寄存器的时候, 你给我在rd中放上rs1+rs2+flag.carried这个结果。为了保证这一点,你必须实现你的逻 辑,让这个“高层逻辑”成立。

同样的,你用一样的约束,如果实现的是great_add,你的内在逻辑需要有很大的改变,因 为两个逻辑空间要认知的东西是不一样的。虽然下面的约束还是这些电子管的导通和截断 ,晶体管的饱和区和截止区的变化。但你在这个基础上要模拟的抽象实体不同,它会需要 不同的逻辑组织。

当然,因为这两个逻辑空间有一个很大的东西是一致的(比如加法本身),这两个指令确实 有不少地方是可以合并的。从一个算法切换到另一个算法中也是比较容易的。但如果一开始 就要求“兼容”两套算法。这个设计难度就会完全不同,比如,我们在逻辑上可能需要这样 合并它们:::

    common_add condition, rs1, rs2, rd, is_great
            a1=rs1
            a2=rs2
            a3=a1+a2
            if is_great:
                    if flags.c==condition:
                            set_mem8_as_endian(flags.e, rd, a3)

            else:        
                    a3+=flag.carried
                    rd = a3

还是那句话,我也不管你细节上实现在什么东西上,也不管你把下面分多少层,这个逻辑 都是横亘在你的整个逻辑空间中的,你怎么都逃不过去,玩什么“这部分放软件上,那部分 放硬件上,那个计算合并在DDRC中……”,都白搭,最后这个逻辑都是要被满足的。

所以,你不能把这些东西推给细节逻辑。高以下为基,确定了高层逻辑,下一层逻辑就被 限制得死死的。我们用高层逻辑去约束细节逻辑(以便降低设计和维护成本),而用细节 逻辑去挑战高层逻辑(以便确认高层逻辑是可实施的),都是为了用逻辑去丈量我们的目 标是否是可以走通的。你可以用细节逻辑去证明高层逻辑不可行,但不能用细节逻辑去证 明高层逻辑可行。你也可以用高层逻辑去约束细节逻辑,但你不能高层设计去证明细节设 计“可行”。我们只是基于目标去建造一个对细节约束最小的逻辑,然后一路向前探路,希 望我们最终可以到达目标而已。

而动不动做“通用设计”,多接口兼容,就是提前收缩自己自由空间,增加细节逻辑空间的 复杂度,最终增加自己失败的可能性。

客观情况和主动控制

在实际的设计中,如何认知现实的条件和主动控制的边界决定了一个构架设计的成败。

比如说,你要做一个SoC的操作系统使能,操作系统是已经存在的,比如是个Linux,或者 是个BSD。指令可能已经存在,也可能增加了一些新的。另外一些基础框架,比如内存的位 置,中断控制器的布局,MMU、IOMMU的细节等,都需要调整,还有各种外部设备的驱动, 也需要适配到各个子系统中。

这种高复杂度的系统,就会让很多构架师无所适从,不知道怎么建模。这样,他们很容易 就走三个极端:

第一,高度抽象。比如,他们的构架会变成一组类似这样的Rules:

  1. 修改要考虑前后兼容,不要出现旧代码不能兼容新硬件的情况;

  2. 严格按照软硬件接口定义的范围获取所需的值;

  3. Driver不可以和芯片演进潜在的可能变化信息绑定;

  4. ……

这些东西,作为一个高层“欲望”是可以的(但也不好,因为它们并不直接匹配有收益的欲 望,不上不下的),但它算不上是设计,因为这东西不制造直接的约束。

第二,给一些差不多的描述和定义,没有明确指意。比如说,他们说“设备”都需要有标识 ,以便驱动可以根据标识匹配不同的SoC。

这句话说出来,你去细问,什么是“设备”?DDRC算不算“设备”?需不需要有标识?L3Cache 又算不算设备?CPU核里面的LSU算不算设备?怎么才叫有标识?mac地址算不算标识?……这些 在作者头脑中都是没有具体指向的。他们这个定义在谈低功耗的时候指PCIe和ACPI HID设 备,谈RAS的时候指所有可以传播错误的设备,谈地址翻译的时候表示带IOMMU翻译的设备… …这样只有个“设计”的样子,其实根本就没有设计,特别是没有构架设计,只能算是近似的 编码。我们在编码前做个设计,就是怕我们编码的时候被细节左右,把某个可以发生的需 求,分支,功能,自相矛盾给错过了,就是要发现丢什么的,你指意不清,在“防丢”这个 目的完全放弃了。

第三,变成细节设计。无穷无尽地去穷举:网卡驱动怎么办,IOMMU驱动怎么办,LED驱动 怎么办,Cache控制接口怎么办……这样弄,全集倒真的是全集了,问题是这直接编码好了, 还设计啥?

架构的概念系统是对上面几个问题的平衡组合:我们先从一个模糊的概念出发,这时指意 确实是不明确的,然后我们细化这个模糊概念的边缘(注意,是边缘,不是全部),这个 细化的过程中,我们发现有另类的东西,无法实施我们的策略,我们建另一个集合去容纳 它。这样,我们就能整理出一个高层的规律。

比如说,对于上面这个SoC的问题,假设我们针对“前后兼容”这个目标建概念空间。我们要 让老软件可以在新SoC上运行。首先,我们引入第一个基本约束(也可以认为是假设):所 有这个系列的SoC必须支持一个可以前后兼容的指令集。这个指令集的基本功能在全系列都 是一样的,只是有不同的Plugin特性(我们这里称为这个指令集的Capabilities),我们 根据Capabilities决定具体是否提供特定的功能。这样我们基本问题解决了,至少在不使 用外设的时候,启动起来肯定是没有问题的。

然后我们考虑外设,我们认为我们所有的外设都被实现为PCIe设备,然则,我们可以用PCI 的VendorID, DeviceID, Version, Revision来区分不同的驱动,在这个基础上,我们就可 以实施我们的高层逻辑了(如何匹配,Version的分配原则,具体某个SoC版本和这些版本 的匹配关系等等)。

好了,我们开始细化边界,比如PCIe的Root Bridge是什么PCIe设备?这里逻辑死循环了。 还有,前面这个基本系统是否包括基本的console端口?这个端口是否需要是PCIe设备?前 面的逻辑被细节逻辑所破坏了。我们引入一个新概念:平台设备,我们把RB定义为平台设 备,如果是平台设备,它有ACPI的HID 名字和版本进行表示,名字和版本同样有一套方法 ,映射到我们前面定义的规则上,同时,我们把最基本的Console设备定义为SoC基本功能 的一部分。这样我们又构造了一个空间。

接着我们用细节去挑战我们这个框架:比如,我们过去的mac设备,是在发现网卡设备的时 候,从网卡的EPROM里面读出参数的,它既没有PCI的发现过程,也没有ACPI的描述。这怎 么处理?一个办法是,我们可以仿照前一个方法,加一种分类。但我们还可能发挥我们的 主动性,要求这类设备由BIOS主动读出,也实现为一个HID设备,这样,这个逻辑就被复合 到前一个设计中了。这才是在做创造性的设计,如果现实是啥样就按啥样做,我们就不需 要做架构设计了,每个做细节设计的人直接去设计不就没有这个问题了?

我们强调架构设计要服从客观规律,是在权衡成本,不让头脑的欲望超越了现实的限制, 但架构设计本身是一种创造性的工作,不表示你完全不去控制这个世界的。