本节摘要:ALU 是组合逻辑章节的收官模块。本节实现八种运算、零标志与溢出标志的八位 ALU,覆盖补码减法、有符号溢出判断、除二移位等工程细节,并首次解读综合报告中的资源占用。
给袖珍计算器的运算单元定规格:八位数据输入两个、运算码三位(八种运算)、结果八位,外加两个标志——零标志 zero(结果为零时拉高)与溢出标志 ovf(有符号运算超出表示范围时拉高)。运算集合定为:加、减、按位与、按位或、按位异或、逻辑左移一位、算术右移一位、按位取反。接口先写死,后面不许中途改——接口一改,测试台与下游模块全要跟着动,这是模块化开发的基本纪律。
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity alu8 is port ( a, b : in std_logic_vector(7 downto 0); op : in std_logic_vector(2 downto 0); y : out std_logic_vector(7 downto 0); zero : out std_logic; ovf : out std_logic ); end entity alu8;

架构的核心是 case 分支表。两个易错点提前说:减法用补码——a - b 等价于 a + not b + 1,但直接写减号综合器会自动翻译,无需手动取反加一;溢出判断只对有符号加减有意义——两正数相加得负、或两负数相加得正,即溢出。
architecture rtl of alu8 is signal ua, ub : unsigned(7 downto 0); signal add_r : unsigned(8 downto 0); -- 加法扩一位看进位 signal y_inner : std_logic_vector(7 downto 0); begin ua <= unsigned(a); ub <= unsigned(b); add_r <= resize(ua, 9) + resize(ub, 9); process (a, b, op, add_r) variable sa, sb : signed(7 downto 0); begin y_inner <= (others => '0'); ovf <= '0'; sa := signed(a); sb := signed(b); case op is when "000" => y_inner <= std_logic_vector(ua + ub); -- 加 when "001" => y_inner <= std_logic_vector(ua - ub); -- 减 when "010" => y_inner <= a and b; -- 与 when "011" => y_inner <= a or b; -- 或 when "100" => y_inner <= a xor b; -- 异或 when "101" => y_inner <= a(6 downto 0) & '0'; -- 左移一位 when "110" => y_inner <= a(7) & a(7 downto 1); -- 算术右移 when others => y_inner <= not a; -- 取反 end case; -- 溢出:仅加减时有意义,正正得负或负负得正 if (op = "000" or op = "001") then if (sa > 0 and sb > 0 and signed(y_inner) < 0) or (sa < 0 and sb < 0 and signed(y_inner) > 0) then ovf <= '1'; end if; end if; end process; y <= y_inner; zero <= '1' when y_inner = (y_inner'range => '0') else '0'; end architecture rtl;
几处写法值得咀嚼。算术右移保留符号位:a(7) & a(7 downto 1),最高位复制一份再整体右移——这与逻辑右移补零是两回事,处理有符号数时选错就悄悄丢符号。y_inner 与 ovf 在 case 前先给默认值,锁存器从源头断根。变量 sa、sb 只做符号视图方便判断,真实数据流走的是信号——变量当便签的纪律再次兑现。
测试台沿用穷举思路,但输入空间是二的十九次方,全枚举不现实——组合电路验证退而求其次:每档运算码枚举代表性输入对,加减覆盖正正、负负、正负、边界值与溢出边界,逻辑与移位各抽全零、全一、交替码。期望值在测试台里用整型重算,逐拍比对。跑完修正直到零差异。
综合之后打开报告找资源统计:LUT(查找表)数量、进位链使用。做两个对照实验:实验一把 op 从三位减到两位(砍掉一半运算),看 LUT 降多少——体会运算档位与面积的线性关系;实验二把减法分支改写补码手动版本 resize(ua,9) + resize(not ub,9) + 1,对比报告——你会发现资源几乎不变,工具早就做了同样的化简。这两个实验建立"代码写法"与"电路规模"之间的手感。
⚠️ 常见坑:溢出标志误用无符号进位。carry(无符号进位)与 overflow(有符号溢出)是两个标志:一百二十七加一,carry 是零、overflow 是一。做无符号计数器关心前者,做有符号算术关心后者,混用的 bug 在边界值才现身,仿真必须覆盖到。
💡 关键直觉:ALU 的电路形态是"所有运算并行展开、多路选择器挑答案",不是"按指令依次执行"。op 稳定后所有运算结果同时就绪,选择的延迟只是选择器一级——这就是组合电路的"指令周期"。
a(7) & a(7 downto 1),与逻辑右移严格区分;