我最近开始阅读 Charles Petzold 的书《Code》,到目前为止,这本书完全涵盖了我认为您感兴趣的内容。但我还没有完全读完,所以在购买/借阅之前先浏览一下这本书。
这是我相对简短的答案,不是 Petzolds……希望符合您的好奇心。
我想你听说过晶体管。使用晶体管的最初方法是用于晶体管收音机之类的东西。它基本上是一个放大器,将漂浮在空气中的微小无线电信号馈送到晶体管的输入端,晶体管打开或关闭旁边电路上的电流。你用更高的功率连接该电路,这样你就可以获取一个非常小的信号,放大它并将其馈送到扬声器中,例如收听广播电台(还有更多隔离频率和保持晶体管平衡的功能,但是我希望你能明白)。
现在晶体管的存在导致了一种使用晶体管作为开关的方法,就像电灯开关一样。收音机就像一个调光开关,您可以将其切换到任何位置,从一直开到一直关。非调光灯开关要么全部打开,要么全部关闭,开关中间有一个神奇的地方可以切换。我们在数字电子产品中以同样的方式使用晶体管。获取一个晶体管的输出并将其馈送到另一个晶体管的输入。第一个晶体管的输出当然不是像无线电波那样的小信号,它迫使第二个晶体管一直打开或一直关闭。这就引出了 TTL 或晶体管-晶体管逻辑的概念。基本上你有一个晶体管驱动高电压或者我们称之为 1,然后它吸收零电压,我们称之为 0。然后你将输入与其他电子设备一起排列,这样你就可以创建与门(如果两个输入是 1,则输出是 1),或门(如果一个或另一个输入是 1,则输出是 1)。反相器、NAND、门、或非门(一个或一个反相器)等。曾经有一本 TTL 手册,你可以购买 8 个左右引脚的芯片,其中有一个或两个或四个某种门(NAND、NOR、 AND 等)内部函数,每个有两个输入和一个输出。现在我们不需要这些,创建具有数百万个晶体管的可编程逻辑或专用芯片会更便宜。但我们仍然从硬件设计的“与”、“或”和“非”门的角度来思考。 (通常更像nand 和nor)。
我不知道他们现在教什么,但概念是相同的,对于内存来说,触发器可以被认为是两个这样的 TTL 对 (NAND),其中一个的输出连接到另一个的输入。让我们就这样吧。这基本上是我们所说的 SRAM 或静态 RAM 中的一个位。 sram 基本上需要 4 个晶体管。 DRAM 或动态 RAM(您自己放入计算机中的记忆棒,每一位都占用一个晶体管),因此对于初学者来说,您可以明白为什么 DRAM 是您购买的价值千兆字节的东西。只要电源不中断,Sram 位就会记住您的设置。一旦你告诉它,Dram 就开始忘记你告诉它的内容,基本上 dram 以第三种不同的方式使用晶体管,有一些电容(如电容器,这里不会讨论)就像一个微型可充电电池,一旦你给它充电并拔掉充电器,它就开始耗尽。想象一下架子上有一排玻璃杯,每个玻璃杯上都有小孔,这些是你的碎片,你想要其中一些是那些,所以你有一个助手来填充你想要成为的玻璃杯。那个助手必须不断地把水罐装满,然后沿着一排走下去,让“一”位的玻璃杯装满水,让“零”位的玻璃杯保持空状态。因此,任何时候您想查看数据是什么,您都可以通过查找绝对高于中间值的水位为 1 以及绝对低于中间值的水位为 0 来查看并读取 1 和 0。打开电源后,如果助手无法将眼镜保持足够满以区分一和零,它们最终都会看起来像零并耗尽。这是每个芯片更多位数的权衡。这里的简短故事是,在处理器之外,我们使用 DRAM 作为大容量内存,并且有辅助逻辑负责保持 1 和 0 为 0。但在芯片内部,AX 寄存器和 DS 寄存器等使用触发器或 sram 保存数据。对于您所了解的每一位(例如 AX 寄存器中的位),可能有数百、数千或更多位用于将这些位移入或移出该 AX 寄存器。
您知道处理器以一定的时钟速度运行,目前约为 2 GHz 或每秒 20 亿个时钟。想想时钟,它是由晶体产生的,另一个话题,但逻辑将时钟视为一个电压,在这个时钟速率 2ghz 或任何其他频率下变高、变高、变为零(gameboy advance 为 17mhz,旧 ipod 约为 75mhz,原装 IBM 电脑 4.77mhz)。
因此,用作开关的晶体管使我们能够获取电压并将其转换为我们作为硬件工程师和软件工程师所熟悉的 1 和 0,甚至为我们提供 AND、OR 和 NOT 逻辑功能。我们拥有这些魔法晶体,可以让我们获得准确的电压振荡。
所以我们现在可以做这样的事情,如果时钟是 1,并且我的状态变量表示我处于获取指令状态,那么我需要切换一些门,以便我想要的指令的地址位于程序计数器,在内存总线上输出,以便内存逻辑可以给我 MOV AL,61h 的指令。你可以在 x86 手册中查找这一点,并发现其中一些操作码位表示这是一个 mov 操作,目标是 EAX 寄存器的低 8 位,而 mov 的源是一个立即数,这意味着它位于该指令之后的内存位置。因此,我们需要将该指令/操作码保存在某处,并在下一个时钟周期获取下一个内存位置。所以现在我们已经保存了mov al,immediate,我们从内存中读取了值61h,我们可以切换一些晶体管逻辑,以便该61h的位0存储在al的位0触发器中,位1存储在位1中,等等。
你问这一切是如何发生的?考虑一个执行某些数学公式的 Python 函数。您从程序的顶部开始,以变量的形式输入公式的一些输入,您可以通过程序执行单独的步骤,这些步骤可能会在此处添加常量或从库中调用平方根函数等。在底部,您可以返回答案。硬件逻辑也是以同样的方式完成的,如今使用的编程语言之一看起来很像 C。主要区别是您的硬件函数可能有数百或数千个输入,而输出是单个位。在每个时钟周期,AL 寄存器的位 0 都会使用一个庞大的算法进行计算,具体取决于您想要查看的范围。考虑一下您为数学运算调用的平方根函数,该函数本身是这些输入产生输出的函数之一,并且它可能会调用其他函数(可能是乘法或除法)。因此,您可能在某个地方有一个位,您可以将其视为 AL 寄存器的位 0 之前的最后一步,其功能是:如果时钟为 1,则 AL[0] = AL_next[0];否则 AL[0] = AL[0];但是有一个更高的函数包含从其他输入计算出的下一个位,还有一个更高的函数和一个更高的函数,其中大部分是由编译器创建的,就像你的三行Python可以变成数百或数千行一样汇编程序行。几行 HDL 可以变成数百、数千或更多的晶体管。硬件人员通常不会查看特定位的最低级公式来找出所有可能的输入以及所有可能的 AND、OR 和 NOT,而这些计算所需的时间可能比检查程序生成的汇编器要多。但如果你愿意的话你可以。
关于微编码的注释,大多数处理器不使用微编码。例如,你会喜欢 x86,因为它在当时是一套很好的指令集,但表面上很难跟上现代的步伐。其他指令集不需要微编码,直接按照我上面描述的方式使用逻辑。您可以将微编码视为使用不同指令集/汇编语言的不同处理器,该处理器模拟您在表面上看到的指令集。不像你尝试在 mac 上模拟 windows 或在 windows 上模拟 linux 等那么复杂。微编码层是专门为这项工作而设计的,你可能会认为只有 AX、BX、CX、DX 这四个寄存器,但是有里面还有很多。当然,一个汇编程序可以以某种方式在一个核心或多个核心的多个执行路径上执行。就像闹钟或洗衣机中的处理器一样,微代码程序简单而小,经过调试并烧录到硬件中,希望永远不需要固件更新。至少理想情况下是这样。但就像你的 iPod 或手机一样,你有时确实需要错误修复或其他什么,并且有一种方法可以升级你的处理器(BIOS 或其他软件在启动时加载补丁)。假设您打开电视遥控器或计算器的电池盒,您可能会看到一个孔,您可以在其中看到一些连续的裸露金属触点,可能是三个、五个或多个。对于某些遥控器和计算器,如果您确实愿意,可以重新编程,更新固件。但通常情况下不会,理想情况下遥控器是完美的或完美到足以比电视机更耐用。微编码提供了将非常复杂的产品(数百万、数亿个晶体管)推向市场并修复该领域中重大且可修复的错误的能力。想象一下,您的团队在 18 个月内编写了一个 2 亿行的 Python 程序,并且必须交付它,否则公司将无法提供竞争产品。同样的事情,除了你可以在现场更新的一小部分代码之外,其余的都必须保留在石头上。对于闹钟或烤面包机,如果有错误或需要帮助,您只需将其扔掉并重新购买即可。
如果您深入维基百科或谷歌搜索,您可以查看 6502、z80、8080 和其他处理器等的指令集和机器语言。可能有 8 个寄存器和 250 条指令,您可以从晶体管的数量中感受到,与每个时钟计算触发器中每个位所需的逻辑门序列相比,250 条汇编指令仍然是一种非常高级的语言循环。你的这个假设是正确的。除了微编码处理器之外,这种低级逻辑不能以任何方式重新编程,您必须使用软件修复硬件错误(对于已交付或将要交付且未报废的硬件)。
查一下佩措尔德的书,他在解释方面做得非常出色,远远优于我写的任何东西。