重现 F-14 CADC:比 Intel 4004 更早的“微处理器”
最近国外的大佬们一直在研究 F-14 Tomcat 战斗机上的 CADCCentral Air Data Computer中央空气数据计算机。令人惊讶的是虽然很多人认为 Intel 4004 是世界上第一款微处理器但实际上F-14 的 CADC 在某种意义上同样具备“微处理器”特征而且它还是一个 20 位系统。由于长期处于军事B?M状态这一项目直到 1998 年才被公开因此并没有像 Intel 4004 那样广为人知。CADC 由 Ray Holt 为 Grumman 公司设计用于 F-14 战斗机数据计算。它负责处理飞机上的空气数据并根据输入信息控制多个关键飞行系统。CADC 的功能CADC 会接收来自飞机传感器的数据包括静压动压温度Pitot数据随后计算马赫数空速垂直速度飞行状态参数同时它还会控制可变后掠翼机动襟翼Glove Vanes翼根小翼整个系统实际上承担了 F-14 飞控中的重要部分。CADC 的结构CADC 与 Intel 4004 的区别在于它的微处理器是在多个独立的设备中实现的。该CADC从根本上来说由以下几个部分组成时序生成器——用于生成整个系统的时序信息。控制ROM和序列器——在时序发生器的控制下它使用存储在控制ROM中的指令对操作进行排序。IO桥接器——提供与传感器、DAC和显示器的外部接口PMU – 并行乘法器单元使用 Booth 算法实现 20x20 位有符号小数乘法。PDU – 并行除法单元采用非恢复除法实现 20 位有符号除法。RAS – 随机存取存储器 – 一种临时 RAM可存储 64 x 20 位字。ROM只读存储器用于存储程序和数据SLF – 特殊逻辑功能它实现了一个算法逻辑单元。SL – 转向逻辑PMU、PDU、SLF 和 RAS 之间的路由逻辑如何获得关于CADC的所有信息的呢幸好Ray Holt几年前将他的个人实验记录和设计笔记发布到了网上。有了这些信息就可以开始对CADC的设计进行逆向工程。逆向工程Ray的个人网站上提供了他的工程笔记以及其他一些文档。通过这些资料能够了解CADC的设计及其组成元件。然而存在一个问题那就是这些笔记本是原始 PDF 格式的扫描件。利用AI来读取文档并定义它是如何确定系统运行方式以及模块之间相互连接和通信的。下一步是为每个模块和整个系统制定规范。有趣的是笔记中通常包含可用于验证模块的信息。为了便于验证规范中确定的测试使用唯一的编号这些编号也在测试台上使用以便进行交叉引用。逆向工程的最后一个环节是确保我们理解设计决策的来源。为此建立了一条从原始资料到需求的追溯链接。AI在这里发挥了非常重要的作用因为在控制和监督下能够快速轻松地创建这些规范和可追溯性矩阵。CADC架构CADC 有一些有趣的架构元素首先是为了节省多个设备之间的 I/O它的接口是位串行的。该系统以40个时钟周期运行该周期分为两个阶段。在第一阶段操作数被移入模块进行PMU、PDU等操作。此阶段称为WoI/O字时间。PDU 或 PMU 需要 20 个时钟周期来生成输出结果这段时间称为 Wa算术字时间。PDU 和 PDM 的结果会在下一个 Wo 周期移出并连接到下一个模块。Wo 和 Wa 时间组合起来称为一个操作周期 (OP)。CADC 的主循环在 512 个操作周期内完成即 ROM 地址重置为 0 的时间这使得 CADC 的扫描速率为 18 Hz。模块要求每个模块的需求都根据原始笔记重新制定。该规范包括模块的输入和输出、预期的功能行为以及用于验证实现是否正确的测试。每个模块需求还列出了用于重现这些需求的主要来源和辅助来源。例如PDU 具有以下要求和验证测试。RTL 实现为了重新创建 RTL使用了先前创建的规范以确保符合原始开发和架构。这里再次大量运用了AI来创建RTL模块。为了指导用于编码的人工智能模型我们提供了编码规则来指导RTL的创建过程。确保代码质量良好的方法之一是利用 Blue Pearls 可视化验证套件进行静态分析。------------------------------------------------------------------------------- -- PDU - Parallel Divider Unit (PN 944112) -- F-14A Central Air Data Computer - FPGA Implementation (Bit-Serial I/O) -- -- Implements 20-bit signed fractional division (Q1.19 format). -- Serial data I/O with parallel internal computation using VHDL divide. -- -- Timing: -- WO: Operands shiftin serially (20 bits), previous quotient shifts out -- WA: Parallel divide computes new quotient ------------------------------------------------------------------------------- LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.NUMERIC_STD.ALL; ENTITY pdu IS PORT ( i_clk : IN STD_LOGIC; i_rst : IN STD_LOGIC; -- Timing inputs i_phi2 : IN STD_LOGIC; -- Shift on phi2 i_word_type : IN STD_LOGIC; -- 0WA, 1WO i_t0 : IN STD_LOGIC; -- First bit of word i_t19 : IN STD_LOGIC; -- Last bit of word -- Serial data inputs i_dividend_bit: IN STD_LOGIC; -- Dividend serial input i_divisor_bit : IN STD_LOGIC; -- Divisor serial input -- Serial data outputs o_quotient_bit: OUT STD_LOGIC; -- Quotient serial output o_remainder_bit: OUT STD_LOGIC; -- Remainder serial output -- Status o_busy : OUT STD_LOGIC; o_div_by_zero : OUT STD_LOGIC ); END ENTITY pdu; ARCHITECTURE rtl OF pdu IS -- Input shift registers (shiftin during WO) SIGNAL s_dividend_sr : STD_LOGIC_VECTOR(19 DOWNTO 0) : (OTHERS 0); SIGNAL s_divisor_sr : STD_LOGIC_VECTOR(19 DOWNTO 0) : (OTHERS 0); -- Output shift registers (shift out during WO) -- 20 bits: use T0 skip-shift combinational output compensation (like PMU) SIGNAL s_quotient_sr : STD_LOGIC_VECTOR(19 DOWNTO 0) : (OTHERS 0); SIGNAL s_remainder_sr: STD_LOGIC_VECTOR(19 DOWNTO 0) : (OTHERS 0); -- Latched operands for computation SIGNAL s_dividend_lat: STD_LOGIC_VECTOR(19 DOWNTO 0) : (OTHERS 0); SIGNAL s_divisor_lat : STD_LOGIC_VECTOR(19 DOWNTO 0) : (OTHERS 0); -- Division state machine TYPE t_state IS (IDLE, SETUP, DIVIDING, CORRECTION, DONE); SIGNAL s_state : t_state : IDLE; SIGNAL s_partial_rem : SIGNED(20 DOWNTO 0) : (OTHERS 0); SIGNAL s_div_reg : SIGNED(20 DOWNTO 0) : (OTHERS 0); SIGNAL s_quot_reg : STD_LOGIC_VECTOR(19 DOWNTO 0) : (OTHERS 0); SIGNAL s_bit_cnt : UNSIGNED(4 DOWNTO 0) : (OTHERS 0); SIGNAL s_dividend_neg : STD_LOGIC : 0; SIGNAL s_divisor_neg : STD_LOGIC : 0; SIGNAL s_abs_dividend : UNSIGNED(19 DOWNTO 0) : (OTHERS 0); SIGNAL s_abs_divisor : UNSIGNED(19 DOWNTO 0) : (OTHERS 0); SIGNAL s_busy : STD_LOGIC : 0; SIGNAL s_dbz_reg : STD_LOGIC : 0; SIGNAL s_compute_done : STD_LOGIC : 0; BEGIN -- Serial outputs: T0 outputs bit(0), T1 outputs bit(1) for same-edge timing o_quotient_bit s_quotient_sr(0) WHEN i_t0 1 ELSE s_quotient_sr(1); o_remainder_bit s_remainder_sr(0) WHEN i_t0 1 ELSE s_remainder_sr(1); o_busy s_busy; o_div_by_zero s_dbz_reg; ----------------------------------------------------------------------------- -- Serial shift process - shift on phi2 during WO ----------------------------------------------------------------------------- shift_proc: PROCESS(i_clk) BEGIN IF RISING_EDGE(i_clk) THEN IF i_rst 1 THEN s_dividend_sr (OTHERS 0); s_divisor_sr (OTHERS 0); s_quotient_sr (OTHERS 0); s_remainder_sr (OTHERS 0); s_dividend_lat (OTHERS 0); s_divisor_lat (OTHERS 0); ELSIF i_phi2 1 AND i_word_type 1 THEN -- WO: Shift in operands (LSB first), shift out results s_dividend_sr i_dividend_bit s_dividend_sr(19 DOWNTO 1); s_divisor_sr i_divisor_bit s_divisor_sr(19 DOWNTO 1); -- Skip shift at T0 for timing compensation (like PMU) IF i_t0 0 THEN s_quotient_sr 0 s_quotient_sr(19 DOWNTO 1); s_remainder_sr 0 s_remainder_sr(19 DOWNTO 1); END IF; -- At end of WO (T19), latch operands for next computation IF i_t19 1 THEN s_dividend_lat i_dividend_bit s_dividend_sr(19 DOWNTO 1); s_divisor_lat i_divisor_bit s_divisor_sr(19 DOWNTO 1); END IF; ELSIF s_compute_done 1 THEN -- Load computed results into 20-bit output shift registers s_quotient_sr s_quot_reg; s_remainder_sr STD_LOGIC_VECTOR(s_partial_rem(19 DOWNTO 0)); END IF; END IF; END PROCESS shift_proc; ----------------------------------------------------------------------------- -- Division process - runs during WA -- For Q1.19 fractional: quotient (dividend * 2^19) / divisor ----------------------------------------------------------------------------- div_proc: PROCESS(i_clk) VARIABLE v_dividend_scaled : UNSIGNED(39 DOWNTO 0); VARIABLE v_quotient_raw : UNSIGNED(39 DOWNTO 0); VARIABLE v_quot : STD_LOGIC_VECTOR(19 DOWNTO 0); BEGIN IF RISING_EDGE(i_clk) THEN IF i_rst 1 THEN s_state IDLE; s_partial_rem (OTHERS 0); s_div_reg (OTHERS 0); s_quot_reg (OTHERS 0); s_bit_cnt (OTHERS 0); s_busy 0; s_dbz_reg 0; s_compute_done 0; ELSE s_compute_done 0; CASE s_state IS WHEN IDLE -- Start computation at beginning of WA IF i_word_type 0 AND i_t0 1 AND i_phi2 1 THEN IF SIGNED(s_divisor_lat) 0 THEN s_dbz_reg 1; s_quot_reg (OTHERS 0); -- Set via s_quot_reg, not directly s_partial_rem (OTHERS 0); s_compute_done 1; ELSE s_dbz_reg 0; s_dividend_neg s_dividend_lat(19); s_divisor_neg s_divisor_lat(19); IF SIGNED(s_dividend_lat) 0 THEN s_abs_dividend UNSIGNED(-SIGNED(s_dividend_lat)); ELSE s_abs_dividend UNSIGNED(s_dividend_lat); END IF; IF SIGNED(s_divisor_lat) 0 THEN s_abs_divisor UNSIGNED(-SIGNED(s_divisor_lat)); ELSE s_abs_divisor UNSIGNED(s_divisor_lat); END IF; s_busy 1; s_state SETUP; END IF; END IF; WHEN SETUP IF i_phi2 1 THEN -- Q1.19 fractional division -- quotient (dividend * 2^19) / divisor -- Scale dividend by 2^19 for fractional result using SHIFT_LEFT v_dividend_scaled : SHIFT_LEFT(RESIZE(s_abs_dividend, 40), 19); -- Perform division IF s_abs_divisor / 0 THEN v_quotient_raw : v_dividend_scaled / RESIZE(s_abs_divisor, 40); v_quot : STD_LOGIC_VECTOR(v_quotient_raw(19 DOWNTO 0)); ELSE v_quot : (OTHERS 0); END IF; -- Apply sign correction: negate quotient if signs differ IF s_dividend_neg / s_divisor_neg THEN s_quot_reg STD_LOGIC_VECTOR(-SIGNED(v_quot)); ELSE s_quot_reg v_quot; END IF; s_partial_rem (OTHERS 0); s_state DONE; END IF; WHEN DIVIDING -- Not used - division is combinational s_state DONE; WHEN CORRECTION -- Not used - correction is donein SETUP s_state DONE; WHEN DONE s_compute_done 1; s_busy 0; s_state IDLE; END CASE; END IF; END IF; END PROCESS div_proc; END ARCHITECTURE rtl;测试平台实现针对每个RTL模块都创建一个测试平台并尽可能参考原始设计规范。除了常规测试例如复位测试、基本性能测试等之外还尽可能使用了笔记本中的原始测试信息。每个测试都有一个唯一的ID该ID与相应的要求相关联。所有创建的测试平台均具备自检功能并由仿真脚本提供支持且设计用于与 Questa 和 Questa Visualizer 配合使用。这使得对设计架构进行研究和探索成为可能。应用程序开发遗憾的是运行在 CADC 上的应用程序没有被保存也无法公开访问。因此可以通过使用笔记中找到的几个简单的“顶级”计算来验证顶级性能。这些计算大多是多项式计算并且都如预期般通过。如果想要一个与 F14 CADC 具有相同功能的东西但由于没有编译器来创建程序所以必须用机器代码级别编写所有程序。AI会按照要求让 Claude 生成一个 ROM 镜像该镜像将执行与之前相同的功能。此应用程序使用 147 条指令Q1, 19 格式计算以下内容▸传感器数据采集— 读取 Ps、Qc、TAT格雷码转二进制转换▸压力比— r Qc / Ps通过 PDU 分压器▸马赫数— 来自 r 的 4 阶霍纳多项式▸高度— 来自 Ps 的 4 阶霍纳多项式▸空速— 由马赫数和 TAT 线性化平方根得到的 TAS▸垂直速度— 按帧速率缩放的高度差▸机翼后掠角— 三次多项式速率受限采用分支定理▸机动襟翼—线性时间表从马赫数开始▸手套叶片— 来自马赫数的二阶多项式框架如下图所示硬件验证在对每个CADC模块的性能以及CADC顶层架构都感到满意后决定将其部署在Spartan 7 FPGA上。这次使用了嵌入式系统模块以及模块载板该载板还包含一个Raspberry Pi Compute Module 5。想法是在FPGA Tile中实现CADCCM5则将传感器数据发送到CADC并读取计算结果。为此将使用之前开发的UART到AXI协议。连接到该协议的还有几个AXI GPIO用于提供激励信号和捕获结果。完整的CADC设计如下所示。然后开发一个 Python 脚本使用户能够更改输入传感器位置并在一个漂亮的 GUI 中查看输出机翼运动。使用的Python脚本是#!/usr/bin/env python3 CADC Interactive Visualization Tool Interactive GUI to explore F-14A CADC behavior with real-time visualization of outputs including wing sweep position.代码太长了放在最后的链接里。参考链接原始设计文档“https://github.com/ATaylorCEngFIET/f14_CADC/tree/main/original_docs其他参考链接“https://firstmicroprocessor.com/“https://www.hackster.io/adam-taylor/recreating-the-f14-cadc-ad92ef“https://github.com/ATaylorCEngFIET/f14_CADC总结这是一个很有意思的项目最终成果是一个很棒的演示。这也凸显了AI在处理遗留和历史项目方面的一个非常有用的应用。我们可以利用AI和我们的工程知识快速创建文档和信息以补充那些文档不完整的遗留设计。这些文档可能因为时间久远而丢失也可能从一开始就从未生成过。Git 仓库“https://github.com/ATaylorCEngFIET/f14_CADC