这一篇在干嘛?
前三章讲了速度、面积、功耗的架构原理,这一章用 AES(高级加密标准)加密核把它们全部实战一遍:同一个加密算法,做出紧凑迭代、部分流水、全流水三种架构,用真实器件数据对比”吞吐量 × 面积”的取舍。读完你会明白:为什么流水线架构在最坏情况下可能退化到和迭代架构一样慢——以及全流水密钥扩展如何救场。
背景:AES 是什么?
AES(原名 Rijndael)是 NIST 在 FIPS PUB 197 中发布的电子数据加密标准,用来取代已被攻破的 DES。它是对称密钥分组密码:把 128 位明文块映射为 128 位密文块,密钥长度可选 128/192/256 位(密钥越长 → 密钥空间越大 → 越安全)。AES 刻意设计得适合硬件实现——这里”容易”意味着不易出错(可靠)且更快(简单的组合逻辑)。本章只深入与架构相关的部分,完整的算法细节请查 FIPS 197。
4.1 AES 的三种架构
4.1.1 算法结构:轮变换与密钥扩展
AES 的加密由四个变换模块组成,每跑一遍叫一”轮(round)“:
| 变换 | 本质 |
|---|---|
| SubBytes(字节替换) | 位映射(查表) |
| ShiftRows(行移位) | 交换(纯布线) |
| MixColumns(列混合) | GF(2⁸) 上的乘法变换 |
| AddRoundKey(轮密钥加) | GF(2) 上的逐位异或 |
轮数由密钥长度决定:128 位 → 10 轮;192 位 → 12 轮;256 位 → 14 轮。每一轮用的轮密钥都不同,由原始密钥经密钥扩展(key expansion)派生——密钥扩展与数据通路并行运行,它是本章架构设计的焦点之一。
密钥扩展规则(设 word = 32 位,N_k = Keysize/32,即 4/6/8):扩展密钥的前 N_k 个字直接填原始密钥;此后每个新的 32 位字 = 前一个字 XOR 再往前第 N_k 个字。当字的位置是 N_k 的整数倍时,要先做变换再异或:先循环移位(RotWord),再对 4 个字节做 8 位映射(SubWord),最后异或一个轮常量 Rcon(FIPS 197 定义为 [x^(i-1), 00, 00, 00],x 即 GF(2⁸) 中的 {02})。
单个密钥扩展单元是独立自治的模块,实现如下:
module KeyExpl1Enc(
// updated values to be passed to next iteration
output [3:0] oKeyIter, oKeyIterModNk,
oKeyIterDivNk,
output [32*`Nk-1:0] oNkKeys,
input iClk, iReset,
// represents total # of iterations and value mod Nk
input [3:0] iKeyIter, iKeyIterModNk,
iKeyIterDivNk,
// The last Nk keys generated in key expansion
input [32*`Nk-1:0] iNkKeys);
// updated values to be passed to next iteration
reg [3:0] oKeyIter, oKeyIterModNk,
oKeyIterDivNk;
reg [32*`Nk-1:0] OldKeys;
reg [31:0] InterKey; // intermediate key value
wire [32*`Nk-1:0] oNkKeys;
wire [31:0] PrevKey, RotWord, SubWord,
NewKeyWord;
wire [31:0] KeyWordNk;
wire [31:0] Rcon;
assign PrevKey = iNkKeys[31:0]; // last word in key array
assign KeyWordNk = OldKeys[32*`Nk-1:32*`Nk-32];
// 1 byte cyclic permutation
assign RotWord = {PrevKey[23:0], PrevKey[31:24]};
// new key calculated in this round
assign NewKeyWord = KeyWordNk ^ InterKey;
// calculate new key set
assign oNkKeys = {OldKeys[32*`Nk-33:0], NewKeyWord};
// calculate Rcon over GF(2^8)
assign Rcon = iKeyIterDivNk == 8'h1 ? 32'h01000000:
iKeyIterDivNk == 8'h2 ? 32'h02000000:
iKeyIterDivNk == 8'h3 ? 32'h04000000:
iKeyIterDivNk == 8'h4 ? 32'h08000000:
iKeyIterDivNk == 8'h5 ? 32'h10000000:
iKeyIterDivNk == 8'h6 ? 32'h20000000:
iKeyIterDivNk == 8'h7 ? 32'h40000000:
iKeyIterDivNk == 8'h8 ? 32'h80000000:
iKeyIterDivNk == 8'h9 ? 32'h1b000000:
32'h36000000;
SboxEnc SboxEnc0(.iPreMap(RotWord[31:24]),
.oPostMap(SubWord[31:24]));
SboxEnc SboxEnc1(.iPreMap(RotWord[23:16]),
.oPostMap(SubWord[23:16]));
SboxEnc SboxEnc2(.iPreMap(RotWord[15:8]),
.oPostMap(SubWord[15:8]));
SboxEnc SboxEnc3(.iPreMap(RotWord[7:0]),
.oPostMap(SubWord[7:0]));
`ifdef Nk8
wire [31:0] SubWordNk8;
// Substitution only when Nk = 8
SboxEnc SboxEncNk8_0(.iPreMap(PrevKey[31:24]),
.oPostMap(SubWordNk8[31:24]));
SboxEnc SboxEncNk8_1(.iPreMap(PrevKey[23:16]),
.oPostMap(SubWordNk8[23:16]));
SboxEnc SboxEncNk8_2(.iPreMap(PrevKey[15:8]),
.oPostMap(SubWordNk8[15:8]));
SboxEnc SboxEncNk8_3(.iPreMap(PrevKey[7:0]),
.oPostMap(SubWordNk8[7:0]));
`endif
always @(posedge iClk)
if(!iReset) begin
oKeyIter <= 0;
oKeyIterModNk <= 0;
InterKey <= 0;
oKeyIterDivNk <= 0;
OldKeys <= 0;
end
else begin
oKeyIter <= iKeyIter + 1;
OldKeys <= iNkKeys;
// update "Key iteration mod Nk" for next iteration
if(iKeyIterModNk + 1 == `Nk) begin
oKeyIterModNk <= 0;
oKeyIterDivNk <= iKeyIterDivNk+1;
end
else begin
oKeyIterModNk <= iKeyIterModNk + 1;
oKeyIterDivNk <= iKeyIterDivNk;
end
if(iKeyIterModNk == 0)
InterKey <= SubWord ^ Rcon;
`ifdef Nk8
// an option only for Nk = 8
else if(iKeyIterModNk == 4)
InterKey <= SubWordNk8;
`endif
else
InterKey <= PrevKey;
end
endmodule点评:
RotWord用一条拼接赋值完成字节循环移位——纯布线操作,零逻辑;- 4 个
SboxEnc实例并行完成 SubWord 的 4 字节映射; oKeyIterModNk(当前迭代序号 mod N_k)用于判断”这个字是否落在 N_k 的倍数上”(决定是否做 SubWord+Rcon 变换);Nk=8时还有额外的每 4 个字一次的替换(`ifdef Nk8分支)——这是 AES-256 特有的规则;- Rcon 用三层条件表达式硬编码,因为它是标准固定的常量表。
数据路径的自治单元则是把一轮的四个变换串起来:
module RoundEnc(
output [32*`Nb-1:0] oBlockOut,
output oValid,
input iClk, iReset,
input [32*`Nb-1:0] iBlockIn, iRoundKey,
input iReady,
input [3:0] iRound);
wire [32*`Nb-1:0] wSubOut, wShiftOut, wMixOut;
wire wValidSub, wValidShift, wValidMix;
SubBytesEnc sub( .iClk(iClk), .iReset(iReset),
.iBlockIn(iBlockIn),
.oBlockOut(wSubOut),
.iReady(iReady),
.oValid(wValidSub));
ShiftRowsEnc shift( .iBlockIn(wSubOut), .oBlockOut(wShiftOut),
.iReady(wValidSub), .oValid (wValidShift));
MixColumnsEnc mixcolumn( .iClk(iClk), .iReset(iReset),
.iBlockIn(wShiftOut),
.oBlockOut(wMixOut),
.iReady(wValidShift),
.oValid(wValidMix),
.iRound(iRound));
AddRoundKeyEnc addroundkey(.iClk(iClk), .iReset(iReset),
.iBlockIn(wMixOut),
.iRoundKey(iRoundKey),
.oBlockOut(oBlockOut),
.iReady(wValidMix),
.oValid(oValid));
endmodule点评:四个子模块用 iReady/oValid 握手信号串联成链,每个子模块知道自己的输出何时有效。假设每个 Round 的延迟是 4 拍(后面按逻辑均衡分配流水级时印证这一点),各级流水线划分如下。
4.1.2 每个子模块的流水级分配
SubBytes:1 级流水。 SubBytes 是 8 位到 8 位的映射,由于迭代特性和映射空间很小,最自然的实现是查表(look-up table)——一个同步的 8×256(2⁸)ROM,带一级流水(图 4.1)。

ShiftRows:0 级流水。 它只是把数据块的行做交换,不需要任何逻辑(纯布线)。如果在这里再插一级流水寄存器,会造成流水级之间逻辑严重不均衡,反而降低最高频率和总吞吐量(图 4.2)——这正是第 1 章”寄存器平衡”思想的反面应用:没有逻辑的地方不要放寄存器。

MixColumns:2 级流水。 它是四个变换里逻辑最多的,是插额外流水级的最佳位置。其层次结构如图 4.3:MixColumns 由 Map-Column 模块构成(图 4.4),Map-Column 又以 Poly-Mult X2(多项式 ×2 乘法器)为基本构件(图 4.5)。



AddRoundKey:1 级流水。 把来自密钥扩展流水线的轮密钥与数据块做异或(图 4.6)。

合计 1+0+2+1 = 4 拍,正好对应前面假设的”每个 Round 延迟 4 时钟”——这就是第 1 章讲的逻辑均衡:流水级加在逻辑最重的地方。
4.1.3 紧凑架构(Compact Architecture)
第一种实现:迭代复用逻辑资源。数据与密钥先在 Initial Round 模块相加并寄存,然后进入加密循环,按顺序流过 SubBytes → ShiftRows → MixColumns → AddRoundKey,每轮结束寄存一次,重复至轮数用完(图 4.7)。这就是第 2 章”卷起流水线”思想的标准应用。

顶层实现:
module AES_Enc_core(
output [32*`Nb-1:0] oCiphertext, // output ciphertext
output oValid, // data at output is valid
output oKeysValid,
input iClk, iReset,
input [32*`Nb-1:0] iPlaintext, // input data to be encrypted
input [32*`Nk-1:0] iKey, // input cipher key
input iReady, // valid data to encrypt
input iNewKey); // signals new key is input
// registered inputs
wire [32*`Nk-1:0] wKeyReg;
wire wNewKeyReg, wReadyReg;
wire [127:0] wPlaintextReg, wBlockOutInit;
wire [127:0] wRoundKeyInit, wRoundKey;
// register inputs
InputRegsEnc InputRegs( .iClk(iClk), .iReset(iReset),
.iKey(iKey),
.iNewKey(iNewKey), .iPlaintext (iPlaintext),
.oKeysValid(oKeysValid),
.iReady(iReady),
.oKey(wKeyReg), .oPlaintext (wPlaintextReg),
.oReady(wReadyReg));
// initial addition of round key
AddRoundKeyEnc InitialKey( .iClk(iClk), .iReset(iReset),
.iBlockIn(wPlaintextReg),
.iRoundKey(wRoundKeyInit),
.oBlockOut(wBlockOutInit),
.iReady(wReadyReg),
.oValid(wValidInit));
// Number of rounds is a function of key size (10, 12, or 14)
// Key expansion block
KeyExpansionEnc KeyExpansion( .iClk(iClk), .iReset (iReset),
.iNkKeys(wKeyReg),
.iReady(wReadyReg),
.oRoundKey(wRoundKey));
RoundsIterEnc RoundsIter( .iClk(iClk), .iReset(iReset),
.iBlockIn(wBlockOutInit),
.oBlockOut(oCiphertext),
.iReady(wValidInit),
.oValid(oValid),
.iRoundKey(wRoundKey));
`ifdef Nk4
assign wRoundKeyInit = wKeyReg[128-1:0];
`endif
`ifdef Nk6
assign wRoundKeyInit = wKeyReg[192-1:192-128];
`endif
`ifdef Nk8
assign wRoundKeyInit = wKeyReg[256-1:256-128];
`endif
endmodule点评:紧凑架构的迭代操作由 KeyExpansionEnc(密钥扩展迭代)和 RoundsIterEnc(数据通路迭代)两个模块承担,每一轮迭代从密钥扩展模块拿一个唯一的轮密钥。初始轮密钥按 N_k 配置取 iKey 的最高 128 位。
密钥扩展的循环复用如下——把单个扩展模块的输出接回自己的输入:
module KeyExpansionEnc(
output [128-1:0] oRoundKey,
input iClk, iReset,
// The last Nk keys generated in initial key expansion
input [32*`Nk-1:0] iNkKeys,
input iReady); // signals a new key is input
wire [3:0] KeyIterIn, KeyIterOut;
wire [3:0] KeyIterDivNkIn, KeyIterDivNkOut;
wire [3:0] KeyIterModNkIn, KeyIterModNkOut;
wire [32*`Nk-1:0] NkKeysOut, NkKeysIn;
wire wReady;
assign wReady = iReady;
assign KeyIterIn = wReady ? `Nk : KeyIterOut;
assign oRoundKey = NkKeysOut[32*`Nk-1:32*`Nk-128];
assign KeyIterModNkIn = wReady ? 4'h0 : KeyIterModNkOut;
assign KeyIterDivNkIn = wReady ? 4'h1 : KeyIterDivNkOut;
assign NkKeysIn = wReady ? iNkKeys : NkKeysOut;
KeyExp1Enc KeyExp1(.iClk(iClk), .iReset(iReset),
.iKeyIter(KeyIterIn),
.iKeyIterModNk(KeyIterModNkIn),
.iNkKeys(NkKeysIn), .iKeyIterDivNk
(KeyIterDivNkIn),
.oKeyIter(KeyIterOut),
.oKeyIterModNk(KeyIterModNkOut),
.oNkKeys(NkKeysOut),
.oKeyIterDivNk(KeyIterDivNkOut));
endmodule点评(图 4.8):KeyExp1Enc 的输出 NkKeysOut / KeyIterOut 等被回环接回自己的输入(非 wReady 时取输出值),每轮迭代用同一套扩展逻辑算下一个字——逻辑为每一轮所复用。wReady 有效时注入初值(迭代计数从 Nk 开始、mod 归零、载入新密钥),相当于状态机的复位。

4.1.4 部分流水架构(Partially Pipelined Architecture)
第二种实现:一轮 AES 用 11~14 个时钟完成(视密钥长度)。如图 4.9,例化多个数据通路核组成流水线,密钥扩展以静态方式完成。以下是 N_k = 4 的代码:

module AES_core(
output [32*`Nb-1:0] oCiphertext, // output ciphertext
output oValid, // data at output is valid
// signals that new key has been completely processed
output oKeysValid,
input iClk, iReset,
input [32*`Nb-1:0] iPlaintext, // input data to be encrypted
input [32*`Nk-1:0] iKey, // input cipher key
input iReady, // valid data to encrypt
input iNewKey); // signals new key is input
wire [32*`Nb-1:0] wRoundKey1, wRoundKey2, wRoundKey3, wRoundKey4, wRoundKey5, wRoundKey6, wRoundKey7, wRoundKey8, wRoundKey9, wRoundKeyFinal, wRoundKeyInit;
wire [32*`Nb-1:0] wBlockOut1, wBlockOut2, wBlockOut3, wBlockOut4, wBlockOut5, wBlockOut6,
wBlockOut7, wBlockOut8,
wBlockOut9, wBlockOutInit;
wire [32*`Nk-1:0] wNkKeysInit;
wire [3:0] wKeyIterInit;
wire [3:0] wKeyIterModNkInit;
wire [3:0] wKeyIterDivNkInit;
wire wValid1, wValid2, wValid3,
wValid4,
wValid5, wValid6, wValid7,
wValid8,
wValid9, wValidFinal,
wValidInit;
wire wNewKeyInit;
wire [128*(`Nr+1)-1:0] wKeys; // complete set of round keys
// registered inputs
wire [32*`Nk-1:0] wKeyReg;
wire wNewKeyReg, wReadyReg;
wire [127:0] wPlaintextReg;
// register inputs
InputRegs InputRegs( .iClk(iClk), .iReset(iReset),
.iKey(iKey),
.iNewKey(iNewKey),
.iPlaintext(iPlaintext),
.iReady(iReady), .oKey(wKeyReg)
.oNewKey(wNewKeyReg),
.oPlaintext(wPlaintextReg),
.oReady(wReadyReg));
// initial key expansion
KeyExpInit KeyExpInit( .iClk(iClk), .iReset(iReset),
.iNkKeys(wKeyReg), .iNewKey
(wNewKeyReg),
.oKeyIter(wKeyIterInit),
.oNewKey(wNewKeyInit),
.oKeyIterModNk
(wKeyIterModNkInit),
.oNkKeys(wNkKeysInit),
.oKeyIterDivNk
(wKeyIterDivNkInit));
// initial addition of round key
AddRoundKey InitialKey( .iClk(iClk), .iReset(iReset),
.iBlockIn(wPlaintextReg),
.iRoundKey(wRoundKeyInit),
.oBlockOut(wBlockOutInit),
.iReady(wReadyReg),
.oValid(wValidInit));
// Key expansion block
KeyExpansion KeyExpansion( .iClk(iClk),
.iReset(iReset),
.iKeyIter(wKeyIterInit),
.iKeyIterModNk(wKeyIterModNkInit),
.iNkKeys(wNkKeysInit),
.iKeyIterDivNk(wKeyIterDivNkInit),
.iNewKey(wNewKeyInit),
.oKeys(wKeys), .oKeysValid (oKeysValid));
// round transformation blocks
Round R1( .iClk(iClk), .iReset (iReset),
.iBlockIn(wBlockOutInit),
.iRoundKey(wRoundKey1),
.oBlockOut(wBlockOut1),
.iReady(wValidInit),
.oValid(wValid1));
Round R9( .iClk(iClk), .iReset (iReset),
.iBlockIn(wBlockOut8),
.iRoundKey(wRoundKey9),
.oBlockOut(wBlockOut9),
.iReady(wValid8),
.oValid(wValid9));
// 10 rounds total
// Initial key addition
assign wRoundKeyFinal = wKeys[128*(`Nr-7)-1: 128*(`Nr-8)];
// round key assignments
assign wRoundKey9 = wKeys[128*(`Nr-6)-1: 128*(`Nr-7)];
assign wRoundKey8 = wKeys[128*(`Nr-5)-1: 128*(`Nr-6)];
assign wRoundKey7 = wKeys[128*(`Nr-4)-1: 128*(`Nr-5)];
assign wRoundKey6 = wKeys[128*(`Nr-3)-1: 128*(`Nr-4)];
assign wRoundKey5 = wKeys[128*(`Nr-2)-1: 128*(`Nr-3)];
assign wRoundKey4 = wKeys[128*(`Nr-1)-1: 128*(`Nr-2)];
assign wRoundKey3 = wKeys[128*`Nr-1: 128*(`Nr-1)];
assign wRoundKey2 = wKeys[128*(`Nr+1)-1: 128*`Nr];
assign wRoundKey1 = wNkKeysInit[128-1:0];
assign wRoundKeyInit = iKey[128-1:0];
FinalRound FinalRound( .iClk(iClk), .iReset(iReset),
.iBlockIn(wBlockOut9),
.iRoundKey(wRoundKeyFinal),
.oBlockOut(oCiphertext),
.iReady(wValid9), .oValid
(oValid));
endmodule点评:
- 中间的 R2~R8 与 R1/R9 结构完全相同(代码里省略),共 10 级 Round 串成流水线;最后一轮
FinalRound与普通 Round 的区别是没有 MixColumns(标准规定); - 密钥扩展先”静态”算完整套轮密钥存进
wKeys(宽度 128×(Nr+1)),各级 Round 按下标切取自己那把轮密钥; - 省略号式的写法说明:这种架构的模块数与轮数成正比,代码量大但结构机械。
这个架构有个致命软肋:如果新密钥的到来速度快于加密速度,外围系统必须”聪明地”等流水线排空(pipe to empty)才能送入新数据块和新密钥——这个状态必须反馈给上游,让数据和密钥被正确缓冲。最坏情况下(每加密一个数据块就换一次密钥),流水线永远填不满,吞吐量退化到与迭代架构相同——那这片巨大的面积就白花了(更别提没达到宣传吞吐量的失落感)。下一节的架构就是为消灭这个问题而生的。
4.1.5 全流水架构(Fully Pipelined Architecture)
“全流水”指的是:密钥扩展与轮变换流水线并行运行,流水线中对应级在恰当的时刻互相供给精确的信息——任何一级、任何数据块的轮密钥只在恰好需要的那一拍有效、被对应的 Round 使用。于是每个数据块都可以用不同的密钥,且延迟和等待状态零惩罚;无论密钥怎么换,轮变换流水线的最大吞吐量始终保持(图 4.10)。

要做到这一点,时序必须非常精确:
- 密钥扩展单次迭代(扩展 4 个 32 位字)与”当前正在生成、下轮就要用的密钥”的前一轮完全同步;
- Key Expansion 块的时钟延迟必须与 Round 块相当(典型 1~4 拍);
- 每个密钥扩展块必须在恰好相同数量的时钟周期内生成轮密钥,且密钥呈现给 AddRoundKey 子块时必须有效。
实现手段:把每个密钥扩展块拆成 4 个增量扩展块,每个块按 NIST 规范生成一个字(128/4 = 32 位)的密钥,各给一级流水(图 4.11)。S-box 实现为同步 8×256 ROM;为了保持延迟时序精确,Rcon 计算也要加一级流水(图 4.12)。


还有个细节:密钥流水线必须比数据流水线早一拍完成——因为 AddRoundKey 块要把 XOR 操作的最终结果打迸最后的寄存器,轮密钥必须提前就位。换句话说,密钥扩展块的第 4 拍必须与对应 Round 块的第 3 拍同步。这靠密钥扩展过程开头的初始密钥加法来错相(图 4.13):前 128 位之外的密钥数据在第一个时钟就开始扩展,而数据流水线在第二个时钟才启动(初始 AddRoundKey 操作引入一拍延迟)。

N_k = 4 的顶层实现:
module AES_core(
output [32*`Nb-1:0] oCiphertext, // output ciphertext
output oValid, // data at output is valid
input iClk, iReset,
input [32*`Nb-1:0] iPlaintext, // input data to be encrypted
input [32*`Nk-1:0] iKey, // input cipher key
input iReady); // valid data to encrypt
wire [32*`Nb-1:0] wRoundKey1, wRoundKey2, wRoundKey3, wRoundKey4, wRoundKey5, wRoundKey6, wRoundKey7, wRoundKey8, wRoundKey9, wRoundKeyFinal, wRoundKeyInit;
wire [32*`Nb-1:0] wBlockOut1, wBlockOut2, wBlockOut3, wBlockOut4, wBlockOut5, wBlockOut6, wBlockOut7, wBlockOut8, wBlockOut9, wBlockOutInit;
wire [32*`Nk-1:0] wNkKeys1, wNkKeys2, wNkKeys3, wNkKeys4, wNkKeys5, wNkKeys6, wNkKeys7, wNkKeys8, wNkKeys9, wNkKeysFinal, wNkKeysInit;
wire [3:0] wKeyIter1, wKeyIter2, wKeyIter3, wKeyIter4, wKeyIter5, wKeyIter6, wKeyIter7, wKeyIter8, wKeyIter9, wKeyIterFinal, wKeyIterInit;
wire [3:0] wKeyIterModNk1, wKeyIterModNk2, wKeyIterModNk3, wKeyIterModNk4, wKeyIterModNk5, wKeyIterModNk6, wKeyIterModNk7, wKeyIterModNk8, wKeyIterModNk9, wKeyIterModNkFinal, wKeyIterModNkInit;
wire [3:0] wKeyIterDivNk1, wKeyIterDivNk2, wKeyIterDivNk3, wKeyIterDivNk4, wKeyIterDivNk5, wKeyIterDivNk6, wKeyIterDivNk7, wKeyIterDivNk8, wKeyIterDivNk9, wKeyIterDivNkFinal, wKeyIterDivNkInit;
wire wValid1, wValid2, wValid3, wValid4, wValid5, wValid6, wValid7, wValid8, wValid9, wValidFinal, wValidInit;
// Initial key addition
assign wRoundKeyInit = wKeyReg[32*`Nk-1:32*`Nk-128];
// round key assignments
assign wRoundKey1 = wNkKeysInit[32*`Nb-1:0];
assign wRoundKey2 = wNkKeys1[32*`Nb-1:0];
assign wRoundKey3 = wNkKeys2[32*`Nb-1:0];
assign wRoundKey4 = wNkKeys3[32*`Nb-1:0];
assign wRoundKey5 = wNkKeys4[32*`Nb-1:0];
assign wRoundKey6 = wNkKeys5[32*`Nb-1:0];
assign wRoundKey7 = wNkKeys6[32*`Nb-1:0];
assign wRoundKey8 = wNkKeys7[32*`Nb-1:0];
assign wRoundKey9 = wNkKeys8[32*`Nb-1:0];
// register inputs
InputRegs InputRegs( .iClk(iClk), .iReset(iReset),
.iKey(iKey),
.iPlaintext(iPlaintext),
.iReady(iReady), .oKey(wKeyReg),
.oPlaintext(wPlaintextReg),
.oReady(wReadyReg));
// initial key expansion
KeyExpInit KeyExpInit( .iClk(iClk), .iReset(iReset),
.iNkKeys(wKeyReg),
.oKeyIter(wKeyIterInit),
.oKeyIterModNk(wKeyIterModNkInit),
.oNkKeys(wNkKeysInit),
.oKeyIterDivNk
(wKeyIterDivNkInit));
// initial addition of round key
AddRoundKey InitialKey( .iClk(iClk), .iReset(iReset),
.iBlockIn(wPlaintextReg),
.iRoundKey(wRoundKeyInit),
.oBlockOut(wBlockOutInit),
.iReady(wReadyReg),
.oValid(wValidInit));
// Number of rounds is a function of key size (10, 12, or 14)
// Key expansion blocks
KeyExpBlock KeyExpBlock1( .iClk(iClk), .iReset(iReset),
.iKeyIter(wKeyIterInit),
.iKeyIterModNk(wKeyIterModNkInit),
.iNkKeys(wNkKeysInit),
.iKeyIterDivNk(wKeyIterDivNkInit),
.oKeyIter(wKeyIter1),
.oKeyIterModNk(wKeyIterModNk1),
.oNkKeys(wNkKeys1),
.oKeyIterDivNk(wKeyIterDivNk1));
KeyExpBlock KeyExpBlock8( .iClk(iClk), .iReset(iReset),
.iKeyIter(wKeyIter7),
.iKeyIterModNk(wKeyIterModNk7),
.iNkKeys(wNkKeys7),
.iKeyIterDivNk(wKeyIterDivNk7),
.oKeyIter(wKeyIter8),
.oKeyIterModNk(wKeyIterModNk8),
.oNkKeys(wNkKeys8),
.oKeyIterDivNk(wKeyIterDivNk8));
// round transformation blocks
Round R1( .iClk(iClk), .iReset (iReset),
.iBlockIn(wBlockOutInit),
.iRoundKey(wRoundKey1),
.oBlockOut(wBlockOut1),
.iReady(wValidInit),
.oValid(wValid1));
...
Round R9( .iClk(iClk), .iReset (iReset),
.iBlockIn(wBlockOut8),
.iRoundKey(wRoundKey9),
.oBlockOut(wBlockOut9),
.iReady(wValid8),
.oValid(wValid9));
// 10 rounds total
assign wRoundKeyFinal = wNkKeys9[32*`Nb-1:0];
KeyExpBlock KeyExpBlock9( .iClk(iClk), .iReset(iReset),
.iKeyIter(wKeyIter8),
.iKeyIterModNk (wKeyIterModNk8),
.iNkKeys(wNkKeys8),
.iKeyIterDivNk (wKeyIterDivNk8),
.oKeyIter(wKeyIter9),
.oKeyIterModNk (wKeyIterModNk9),
.oNkKeys(wNkKeys9),
.oKeyIterDivNk(wKeyIterDivNk9));
FinalRound FinalRound( .iClk(iClk), .iReset(iReset), .iBlockIn(wBlockOut9), .iRoundKey(wRoundKeyFinal), .oBlockOut(oCiphertext), .iReady(wValid9), .oValid (oValid));
endmodule点评:
- 与部分流水架构最大的不同:不再是”一个密钥扩展器算完全部”,而是
KeyExpBlock1~KeyExpBlock9每级 Round 旁边都配一个密钥扩展块,密钥像数据一样逐级流水地生成; - 对比前面部分流水的代码——那里所有轮密钥从静态大数组
wKeys切片取得,而这里wRoundKeyN = wNkKeys(N-1),来自上一级扩展块刚流出来的值; - 代价显而易见:S-box、Rcon 等资源每一级都要复制一份,面积暴涨——这正是 4.2 节数据表里的 5894 个 LUT。
4.2 性能与面积的对比
三种架构用同一套 Verilog 和相同的编码规范实现,分别目标到两种工艺。先看 Xilinx Virtex II FPGA 的数据(表 4.1):
| 架构 | 面积(Xilinx LUTs) | 最佳吞吐量(Mbps) | 最坏情况吞吐量 |
|---|---|---|---|
| 迭代(Iterative) | 886 | 340 | 340 |
| 部分流水(Partially Pipelined) | 4432 | 15,400 | 314 |
| 全流水(Fully Pipelined) | 5894 | 15,400 | 15,400 |
(最坏情况吞吐量假设每个数据块都引入新密钥。)
再对比 0.35 μm AMI ASIC 工艺(表 4.2):
| 架构 | 面积(ASIC 门) | 最佳吞吐量(Mbps) | 最坏情况吞吐量 |
|---|---|---|---|
| 迭代 | 3321 | 788 | 788 |
| 部分流水 | 15,191 | 40,064 | 817 |
| 全流水 | 25,758 | 40,064 | 40,064 |
(最坏情况吞吐量假设每个数据块都引入新密钥。)
指标定义:
- LUTs:AES 核在 FPGA 内消耗的逻辑利用率;
- ASIC 门:AES 核在 ASIC 中消耗的逻辑门数;
- 最佳吞吐量(Best possible throughput):最好情况(新密钥扩展带来的惩罚延迟最小)下每秒能处理的最大数据位数;
- 最坏情况吞吐量(Worst-case throughput):最坏情况(每个数据块都有唯一密钥、密钥扩展惩罚最大)下的吞吐量。
读表的三个关键观察:
- 全流水架构在最坏情况(每个数据块换一把密钥)下比其他架构快两个数量级(15,400/340 ≈ 45 倍;ASIC 上 40,064/788 ≈ 51 倍);
- 部分流水架构是”陷阱”:最佳吞吐与全流水相同(15,400),但最坏情况暴跌到 314——比迭代架构还慢!因为它在频繁换钥时必须等流水线排空,完全填不满。架构选型必须看最坏情况,而不是厂商宣传的最佳情况;
- 面积上全流水是迭代架构的约 6.7 倍(FPGA)/ 7.7 倍(ASIC)。吞吐量是用面积换来的——完美呼应第 1、2 章的展开/折叠权衡。
4.3 其他优化
从对比数据看,全流水设计的首要问题是面积利用率,其中大头是存放 S-box 查找表的块 RAM(BRAM)数量。现代实现普遍用 BRAM 查表,因为它实现简单、一拍出结果。但算一笔账:GF(2⁸) 上的任意映射需要一块 8×256 RAM;单个映射不是问题,可每个时钟大约要做 320 次查表——这对 FPGA 是重大压力,内存需求几乎触及大型 FPGA 的上限。
另一条路是用扩展欧几里得算法(Extended Euclidean Algorithm)直接计算乘法逆元来代替查表。但在 GF(2^m) 中求任意多项式的逆,复杂度是 O(m),需要 2m 步计算;而且每步依赖上一步的结果,无法并行——这种迭代算法软件里无所谓,硬件实现的延迟(2×m×轮数 = 160~224 个时钟)对频繁加密小块数据的应用来说不可接受。
第三种方案由 Rijmen(Rijndael 的发明人之一)提出:把 GF(256) 的每个元素表示为系数取自 GF(16) 的一次多项式。设不可约多项式为 x² + Ax + B,任意多项式 bx + c 的乘法逆元为:
点评:这样一来,“在 GF(256) 中求逆”被降维成”在 GF(16) 中求逆 + GF(16) 上的一些算术运算”。GF(16) 的逆元表比 GF(256) 的映射表小得多——可以做出更紧凑的 S-box,直接缓解全流水架构的 BRAM 压力。这是”算法级重构换取硬件友好性”的经典案例。
4.4 本章要点速查
- AES 一轮 = SubBytes(查表,1 级流水)+ ShiftRows(纯布线,0 级)+ MixColumns(逻辑最重,2 级)+ AddRoundKey(异或,1 级),合计 4 拍——流水级按逻辑量均衡分配。
- 紧凑架构:Round 和密钥扩展都迭代复用一套逻辑(输出回环接输入),面积最小、吞吐最低。
- 部分流水架构:数据通路级联成流水线、密钥静态算完;最佳吞吐高,但频繁换钥时退化到迭代架构的速度。
- 全流水架构:密钥扩展逐级与数据流水线并行对齐(密钥比数据早一拍就位),任何密钥模式下都保持最大吞吐,代价是面积数倍。
- 选架构要看最坏情况吞吐量:真实系统里密钥更换频率由应用决定,不由你决定。
- S-box 的面积优化可从算法下手:GF(256) → GF(16) 的复合域分解,查表规模指数级缩小。
常见坑
最容易踩的坑是按最佳吞吐量选架构:看到部分流水架构”15,400 Mbps”的指标就拍板上马,结果应用场景是”每个数据块一把密钥”(如网络加密中密钥频繁协商的场景),实测吞吐只有 314 Mbps——比 886 LUT 的迭代架构还慢一半,却花了 4432 个 LUT。教训:密钥更换频率是架构选型的第一输入,评估时永远用 worst-case 吞吐量,并要求实现后分析确认流水线填充策略。
通关标准:
学完本篇你应该能做到:
- 画出 AES 一轮的四个变换及其流水级分配,并解释”为什么 ShiftRows 是 0 级、MixColumns 是 2 级”;
- 用”输出回环接输入”的模式把一个自治单元改造成迭代复用结构(紧凑架构的核心手法);
- 解释部分流水架构最坏情况退化的机理(流水线填充被密钥扩展阻塞)和全流水架构的对齐要求(密钥比数据早一拍);
- 拿着表 4.1/4.2,根据”密钥更换频率 + 面积预算”为具体应用选出正确架构。
自测:AES 一轮的四个变换分别消耗几级流水?为什么这样分配?
SubBytes 1 级(同步 8×256 ROM 查表)、ShiftRows 0 级(纯布线交换,无逻辑)、MixColumns 2 级(四个变换中逻辑最多,是插流水级的最佳位置)、AddRoundKey 1 级(异或后寄存)。合计 4 拍。分配原则是逻辑均衡:流水级加在逻辑最重的地方,没有逻辑的地方不设寄存器,否则会造成流水级间不均衡、降低最高频率。
自测:紧凑架构如何做到"一套逻辑跑完全部轮数"?
两个手段:数据通路上,Round 的输出寄存后回环接回 Round 的输入,反复迭代直到轮数用完;密钥扩展上,KeyExp1Enc 的输出(NkKeysOut、迭代计数等)在非 ready 状态回环接回自己的输入,每轮生成下一个 32 位扩展字。这就是第 2 章”卷起流水线”的完整应用。
自测:部分流水架构在"每个数据块都换密钥"时为什么退化到和迭代架构一样慢?
每换一把新密钥,必须等静态密钥扩展算完全部轮密钥、流水线排空后才能注入新数据块——流水线大部分时间处于排空/等待状态,无法连续填充。此时数据通路的并行结构完全用不上,有效吞吐只剩串行迭代水平,而面积却是迭代架构的 5 倍,纯亏。
自测:全流水架构中,为什么密钥流水线必须比数据流水线早一拍?
AddRoundKey 子块要把 XOR 运算的结果打迸最终寄存器,轮密钥必须在 XOR 发生之前就在它的输入上稳定有效。所以密钥扩展块的第 4 拍要与对应 Round 块的第 3 拍同步,通过在密钥扩展起点做初始密钥加法(让密钥扩展早一拍启动)来错相实现。
自测:S-box 的三种实现思路各有什么取舍?
① BRAM 查表:一拍出结果、实现简单,但全流水架构每拍约 320 次查表,8×256 RAM 的数量逼近大 FPGA 的 BRAM 上限;② 扩展欧几里得算法求逆:省存储,但 O(m) 复杂度、逐步依赖无法并行,硬件延迟 160~224 拍,频繁加密小块数据时不可接受;③ Rijmen 的复合域方法:把 GF(256) 求逆降维为 GF(16) 求逆,查表规模指数级缩小,兼得速度与紧凑——是算法重构换硬件友好性的典范。