行业新闻

重现 F-14 CADC:比 Intel 4004 更早的“微处理器”

发布 2026年5月20日
/
阅读 9 分钟

最近,国外的大佬们一直在研究 F-14 Tomcat 战斗机上的 CADC(Central 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 算法实现 20×20 位有符号小数乘法。

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等操作。此阶段称为Wo(I/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 shift in 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; — ‘0’=WA, ‘1’=WO 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 (shift in 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 done in 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中实现CADC,CM5则将传感器数据发送到CADC并读取计算结果。为此,将使用之前开发的UART到AXI协议。连接到该协议的还有几个AXI GPIO,用于提供激励信号和捕获结果。

完整的CADC设计如下所示。

然后开发一个 Python 脚本,使用户能够更改输入传感器位置,并在一个漂亮的 GUI 中查看输出机翼运动。

使用的Python脚本是

#!/usr/bin/env python3″””CADC Interactive Visualization ToolInteractive GUI to explore F-14A CADC behavior with real-time visualizationof outputs including wing sweep position.

代码太长了,放在最后的链接里。

参考链接

原始设计文档:

https://github.com/ATaylorCEngFIET/f14_CADC/tree/main/original_docs

其他参考链接:

Home

https://www.hackster.io/adam-taylor/recreating-the-f14-cadc-ad92ef

https://github.com/ATaylorCEngFIET/f14_CADC

总结

这是一个很有意思的项目,最终成果是一个很棒的演示。

这也凸显了AI在处理遗留和历史项目方面的一个非常有用的应用。我们可以利用AI和我们的工程知识,快速创建文档和信息,以补充那些文档不完整的遗留设计。这些文档可能因为时间久远而丢失,也可能从一开始就从未生成过。

如需了解更多,请联系我们

官方业务邮箱(点击发送)

sales@agmcn.com

扫码添加微信直接与工作人员沟通

扫码加微信直接与工作人员沟通

标签

更多推荐