1 PE
下面从模块声明到最后一个 endmodule,逐行解析 PE.sv 的每一句代码。
一、模块声明与参数
module PE #(
parameter PE_WIDTH = 16,
parameter DATA_WIDTH = 16,
parameter NUM_BLOCK = 4,
parameter NUM_PE = 4,
parameter LOG_NUM_PE = $clog2(NUM_PE),
parameter REF_LEN_WIDTH = 10,
parameter QUERY_LEN_WIDTH = 10,
parameter PE_IDX = 0,
parameter LOG_MAX_TILE_SIZE =10
) (
逐参数解释:
| 参数 | 默认值 | 含义 |
|---|---|---|
PE_WIDTH | 16 | 分数(H/I/D)的位宽,有符号数 |
DATA_WIDTH | 16 | 数据位宽(当前未使用,保留) |
NUM_BLOCK | 4 | tile 中 block 的数量(用于回溯地址计算) |
NUM_PE | 4 | PE 阵列中 PE 的个数 |
LOG_NUM_PE | PE 索引位宽 | |
REF_LEN_WIDTH | 10 | ref 坐标位宽 |
QUERY_LEN_WIDTH | 10 | query 坐标位宽 |
PE_IDX | 0 | 当前 PE 在阵列中的位置(0 到 NUM_PE-1) |
LOG_MAX_TILE_SIZE | 10 | marker 位宽 |
二、输入端口
input logic clk,
input logic rst,
input logic init_in,
input logic set_param,
| 信号 | 含义 |
|---|---|
clk | 时钟,上升沿触发 |
rst | 复位,高有效 |
init_in | 初始化脉冲,来自前一个 PE 的 init_out(PE[0] 来自状态机) |
set_param | 参数加载使能,在 SET_PARAM 状态下拉高 |
input param_valid_in,
input logic [2: 0] ref_in,
| 信号 | 含义 |
|---|---|
param_valid_in | 参数有效标志,来自 !query_oflow(query 未越界时为 1) |
ref_in[2:0] | 当前参考碱基(3-bit nt),从 ref_bram 读出后经 ascii2nt 转换 |
input logic [PE_WIDTH - 1: 0] sub_A_in,
input logic [PE_WIDTH - 1: 0] sub_C_in,
input logic [PE_WIDTH - 1: 0] sub_G_in,
input logic [PE_WIDTH - 1: 0] sub_T_in,
input logic [PE_WIDTH - 1: 0] sub_N_in,
input logic [PE_WIDTH - 1: 0] gap_open_in,
input logic [PE_WIDTH - 1: 0] gap_extend_in,
| 信号 | 含义 |
|---|---|
sub_A/C/G/T/N_in | 4 个替换分数 + 1 个 N 分数,由 nt2param 根据 query nt 选出 |
gap_open_in | gap 打开罚分(论文中的 ) |
gap_extend_in | gap 延长罚分(论文中的 ) |
input logic signed [PE_WIDTH - 1: 0] INF,
| 信号 | 含义 |
|---|---|
INF | 负无穷,用于剪枝(越界或初始化为 时使用) |
input logic block,
input [1:0] init_state,
input logic signed [PE_WIDTH - 1: 0] diag_score,
| 信号 | 含义 |
|---|---|
block | 0 表示第一个 block,1 表示后续 block |
init_state[1:0] | 初始状态:0=H, 1=D, 2=I, 3=对角 |
diag_score | 上一个 block 最后一行的 H 分数(用于 block 衔接) |
input logic [REF_LEN_WIDTH - 1: 0] start_idx,
input logic [LOG_MAX_TILE_SIZE : 0] marker,
input logic [REF_LEN_WIDTH - 1: 0] ref_idx_in,
input logic [QUERY_LEN_WIDTH - 1: 0] query_idx_in,
| 信号 | 含义 |
|---|---|
start_idx | 当前 block 在 ref 上的起始索引 |
marker | 当前 tile 的 marker(Phase 1/Phase 2 分界点) |
ref_idx_in | 当前格子的 ref 坐标 |
query_idx_in | 当前格子的 query 坐标 |
input logic [PE_WIDTH - 1: 0] D_init_in,
input logic [PE_WIDTH - 1: 0] H_init_in,
| 信号 | 含义 |
|---|---|
D_init_in | D 状态的初始值 |
H_init_in | H 状态的初始值 |
input logic signed [PE_WIDTH - 1: 0] H_PE_prev,
input logic signed [PE_WIDTH - 1: 0] I_PE_prev,
input logic [REF_LEN_WIDTH + 1: 0] CH_PE_prev,
input logic [REF_LEN_WIDTH + 1: 0] CI_PE_prev,diag_CH_score,
| 信号 | 含义 |
|---|---|
H_PE_prev | 左邻 PE 的 H 分数(用于 I 的 gap open) |
I_PE_prev | 左邻 PE 的 I 分数(用于 I 的 gap extend) |
CH_PE_prev | 左邻 PE 的 H 收敛指针 |
CI_PE_prev | 左邻 PE 的 I 收敛指针 |
diag_CH_score | 对角线来源的收敛指针 |
三、输出端口
output logic init_out,
output logic [3: 0] dir_out,
output logic [2: 0] ref_out,
| 信号 | 含义 |
|---|---|
init_out | 初始化完成信号,传给下一个 PE |
dir_out[3:0] | 4-bit traceback pointer,写入 tb_bram |
ref_out[2:0] | 参考碱基传给下一个 PE(延迟一拍) |
output logic [REF_LEN_WIDTH - 1: 0] ref_idx_out,
output logic [QUERY_LEN_WIDTH - 1: 0] query_idx_out,
| 信号 | 含义 |
|---|---|
ref_idx_out | ref 坐标传给下一个 PE |
query_idx_out | query 坐标传给下一个 PE |
output logic signed [PE_WIDTH - 1: 0] H_PE,
output logic signed [PE_WIDTH - 1: 0] I_PE,
output logic signed [PE_WIDTH - 1: 0] D_PE,
| 信号 | 含义 |
|---|---|
H_PE | 当前格子的 H 分数,传给下一个 PE |
I_PE | 当前格子的 I 分数,传给下一个 PE |
D_PE | 当前格子的 D 分数 |
output logic [REF_LEN_WIDTH + 1: 0] CH_PE,
output logic [REF_LEN_WIDTH + 1: 0] CI_PE,
output logic [REF_LEN_WIDTH + 1: 0] CD_PE
);
| 信号 | 含义 |
|---|---|
CH_PE | H 状态的 convergence pointer |
CI_PE | I 状态的 convergence pointer |
CD_PE | D 状态的 convergence pointer |
位宽说明:REF_LEN_WIDTH + 1 = 11 bits,低 2 位是状态标记(00=H, 01=I, 10=D, 11=marker 前一格),高 9 位是 ref 坐标。
四、局部参数与内部信号
localparam VER = 2, HOR = 1, DIAG = 0;
localparam CONV_DONT_CARE=0;
localparam NU_DONT_CARE=5;
| 参数 | 值 | 含义 |
|---|---|---|
VER | 2 | 垂直方向(dir[1:0] = 2'b10) |
HOR | 1 | 水平方向(dir[1:0] = 2'b01) |
DIAG | 0 | 对角方向(dir[1:0] = 2'b00) |
CONV_DONT_CARE | 0 | 收敛指针不参与比较的标记 |
NU_DONT_CARE | 5 | ref 越界标记(来自 ascii2nt 的 N=0,但 PE 内部用 5 表示越界) |
// dir tb
// ??00 Diagonal H
// ?001 Horizontal H
// ?101 Horizontal D
// 0?10 Vertical H
// 1?10 Vertical I
注释:这是 dir[3:0] 的编码表,说明 4-bit traceback pointer 的含义。
logic signed [PE_WIDTH - 1: 0] sub_A;
logic signed [PE_WIDTH - 1: 0] sub_C;
logic signed [PE_WIDTH - 1: 0] sub_G;
logic signed [PE_WIDTH - 1: 0] sub_T;
logic signed [PE_WIDTH - 1: 0] sub_N;
logic signed [PE_WIDTH - 1: 0] gap_open;
logic signed [PE_WIDTH - 1: 0] gap_extend;
logic signed [PE_WIDTH - 1: 0] match_reward;
logic param_valid;
| 信号 | 含义 |
|---|---|
sub_A/C/G/T/N | 参数寄存器(从 *_in 加载) |
gap_open, gap_extend | gap 罚分寄存器 |
match_reward | 当前格子的匹配/错配分数(组合逻辑) |
param_valid | 参数有效寄存器 |
//logic xdrop_flag_reg;
logic [REF_LEN_WIDTH - 1: 0] ref_idx;
logic [QUERY_LEN_WIDTH - 1: 0] query_idx;
| 信号 | 含义 |
|---|---|
ref_idx | 当前格子的 ref 坐标(寄存器) |
query_idx | 当前格子的 query 坐标(直接来自 query_idx_in,见后面 assign) |
logic signed [PE_WIDTH - 1: 0] HV_score;
logic signed [PE_WIDTH - 1: 0] IV_score;
logic signed [PE_WIDTH - 1: 0] I_score;
logic signed [PE_WIDTH - 1: 0] HH_score;
logic signed [PE_WIDTH - 1: 0] DH_score;
logic signed [PE_WIDTH - 1: 0] D_prev;
logic signed [PE_WIDTH - 1: 0] H_prev;
logic signed [PE_WIDTH - 1: 0] D_score;
logic signed [PE_WIDTH - 1: 0] HD_score;
logic signed [PE_WIDTH - 1: 0] H_score;
| 信号 | 含义 |
|---|---|
HV_score | I 从 H 打开 gap: |
IV_score | I 从 I 延长 gap: |
I_score | |
HH_score | D 从 H 打开 gap: |
DH_score | D 从 D 延长 gap: |
D_score | |
D_prev, H_prev | 同一 PE 上一行的 D、H 分数(寄存器) |
HD_score | 对角线 + 匹配: |
H_score |
logic signed [PE_WIDTH - 1: 0] H_PE_prev_reg;
logic signed [PE_WIDTH - 1: 0] H_PE_prev2_reg;
logic signed [PE_WIDTH - 1: 0] H_prev_reg;
logic signed [PE_WIDTH - 1: 0] I_PE_prev_reg;
logic signed [PE_WIDTH - 1: 0] D_prev_reg;
logic signed [PE_WIDTH - 1: 0] H_PE_prev2;
| 信号 | 含义 |
|---|---|
H_PE_prev_reg | 对齐后的左邻 H(组合逻辑) |
H_PE_prev2_reg | 对齐后的对角线 H(组合逻辑,原点时强制为 0) |
H_prev_reg | 对齐后的上边 H(组合逻辑,初始化时用 H_init_in) |
I_PE_prev_reg | 对齐后的左邻 I(组合逻辑) |
D_prev_reg | 对齐后的上边 D(组合逻辑,初始化时用 D_init_in) |
H_PE_prev2 | 对角线 H 的寄存器(延迟一拍) |
logic [REF_LEN_WIDTH + 1: 0] CH_score;
logic [REF_LEN_WIDTH + 1: 0] CI_score;
logic [REF_LEN_WIDTH + 1: 0] CD_score;
logic [REF_LEN_WIDTH + 1: 0] CH_score_reg;
logic [REF_LEN_WIDTH + 1: 0] CH_score_reg2;
logic [REF_LEN_WIDTH + 1: 0] CD_score_reg;
| 信号 | 含义 |
|---|---|
CH_score | 当前格子的 H 收敛指针(组合逻辑) |
CI_score | 当前格子的 I 收敛指针(组合逻辑) |
CD_score | 当前格子的 D 收敛指针(组合逻辑) |
CH_score_reg | 左边格子的 H 收敛指针(寄存器) |
CH_score_reg2 | 对角线格子的 H 收敛指针(寄存器) |
CD_score_reg | 上边格子的 D 收敛指针(寄存器) |
logic [3: 0] dir;
logic [1:0] H_flag;
logic D_flag;
logic I_flag;
| 信号 | 含义 |
|---|---|
dir[3:0] | 4-bit traceback pointer(组合逻辑) |
H_flag[1:0] | 未使用 |
I_flag | I 是从 H 打开(0)还是从 I 延长(1) |
D_flag | D 是从 H 打开(0)还是从 D 延长(1) |
五、match_reward_calculation 组合逻辑
always_comb begin : match_reward_calculation
case ({ref_in})
3'b111 : match_reward = sub_N;
3'b001 : match_reward = sub_A;
3'b010 : match_reward = sub_C;
3'b011 : match_reward = sub_G;
3'b100 : match_reward = sub_T;
default : match_reward = 0;
endcase
end
逐行解释:
ref_in是 3-bit nt,从 ref_bram 读出后经 ascii2nt 转换。3'b111不是标准 nt 编码,这里应该是 N 的一种表示(代码中NU_DONT_CARE=5也说明越界时 ref_in 可能为 5)。- 根据 ref 的 nt,从
sub_A/C/G/T/N中选出对应的分数。 sub_X是由nt2param根据 query nt 选出的替换矩阵行。- 例如:query 是 A,ref 是 C,则
match_reward = sub_C,对应 W 矩阵中 A-C 的分数。
注意:3'b001=A, 3'b010=C, 3'b011=G, 3'b100=T,这是 ascii2nt 的编码。
六、boundary_conditions 组合逻辑
always_comb begin: boundary_conditions
if (!query_idx && !ref_idx)
H_PE_prev2_reg = 0;
else
H_PE_prev2_reg = H_PE_prev2;
- 如果当前是原点 (
query_idx=0且ref_idx=0),对角线 H 强制为 0。 - 否则,
H_PE_prev2_reg等于寄存器H_PE_prev2。
H_PE_prev_reg = H_PE_prev;
I_PE_prev_reg = I_PE_prev;
H_PE_prev_reg和I_PE_prev_reg直接来自左邻 PE。- 这两个信号用于 I 状态的 gap open 和 gap extend。
if (!init_out&&init_in) begin
H_prev_reg = H_init_in;
D_prev_reg = D_init_in;
end
else begin
H_prev_reg = H_prev;
D_prev_reg = D_prev;
end
end
- 当
init_in=1且init_out=0(初始化脉冲的上升沿)时,用外部初始值H_init_in和D_init_in。 - 否则,使用寄存器
H_prev和D_prev(上一行同 PE 的分数)。
物理意义:这是处理 tile 起点初始化的逻辑。当一个新的 block 或 tile 开始时,PE 需要用外部传入的初始值,而不是自己上一行的值。
七、score_calculation 组合逻辑
这是 PE 最核心的分数计算,对应论文公式 (1)–(3)。
always_comb begin : score_calculation
// I (i,j) calculation
HV_score = H_PE_prev_reg + gap_open;
IV_score = I_PE_prev_reg + gap_extend;
if ($signed(HV_score) >= $signed(IV_score)) begin
I_score = HV_score;
I_flag = 0;
end
else begin
I_score = IV_score;
I_flag = 1;
end
逐行解释:
HV_score = H_PE_prev_reg + gap_open:从 H 状态打开 gap,付gap_open罚分。H_PE_prev_reg是左邻 PE 的 H 分数(因为插入使 query 前进,ref 不动,所以来源是左边的格子)。IV_score = I_PE_prev_reg + gap_extend:从 I 状态延长 gap,付gap_extend罚分。- 如果
HV_score >= IV_score,说明从 H 打开 gap 更优,I_flag = 0。 - 否则,从 I 延长 gap 更优,
I_flag = 1。 I_score是两者最大值。
注意:这里用的是加法,不是减法,因为 gap_open 和 gap_extend 在参数中已经是负值(罚分)。
// D (i,j) calculation
HH_score = H_prev_reg + gap_open;
DH_score = D_prev_reg + gap_extend;
if ($signed(HH_score) >= $signed(DH_score)) begin
D_score = HH_score;
D_flag = 0;
end
else begin
D_score = DH_score;
D_flag = 1;
end
逐行解释:
HH_score = H_prev_reg + gap_open:从 H 状态打开 gap。H_prev_reg是同一 PE 上一行的 H 分数(因为删除使 ref 前进,query 不动,所以来源是上边的格子)。DH_score = D_prev_reg + gap_extend:从 D 状态延长 gap。- 如果
HH_score >= DH_score,D_flag = 0(从 H 打开)。 - 否则,
D_flag = 1(从 D 延长)。 D_score是两者最大值。
// H (i,j) calculation
HD_score = H_PE_prev2_reg + match_reward;
H_score = HD_score;
dir[1:0] = DIAG;
if(D_score>H_score) begin
H_score = D_score;
dir[1:0] = HOR;
end
if(I_score>H_score) begin
H_score = I_score;
dir[1:0] = VER;
end
逐行解释:
HD_score = H_PE_prev2_reg + match_reward:对角线来源。H_PE_prev2_reg是对角线格子的 H 分数,match_reward是匹配/错配分数。- 默认
H_score = HD_score,dir[1:0] = DIAG = 0。 - 如果
D_score > H_score,更新H_score = D_score,dir[1:0] = HOR = 1。 - 如果
I_score > H_score,更新H_score = I_score,dir[1:0] = VER = 2。 - 最终
H_score是三者最大值,dir[1:0]记录来源。
注意:这里用 > 而不是 >=,所以优先级是:对角 > 水平 > 垂直。即如果水平和对角相等,选对角。
if (I_flag) begin
dir[3] = 1;
end
else begin
dir[3] = 0;
end
if (D_flag) begin
dir[2] = 1;
end
else begin
dir[2] = 0;
end
dir[3] = I_flag:记录 I 是从 H 打开(0)还是从 I 延长(1)。dir[2] = D_flag:记录 D 是从 H 打开(0)还是从 D 延长(1)。
4-bit dir 完整编码:
dir[3:0] = ??00:来自对角线dir[3:0] = ?001:来自水平 Hdir[3:0] = ?101:来自水平 Ddir[3:0] = 0?10:来自垂直 Hdir[3:0] = 1?10:来自垂直 I
if(PE_IDX==0&&!block&&init_state!=3&&!init_out) begin
H_score=init_state==0?0:-INF;
I_score=init_state==2?0:-INF;
D_score=init_state==1?0:-INF;
end
逐行解释:
- 只在
PE_IDX==0(第一个 PE)、!block(第一个 block)、init_state!=3(不是对角初始化)、!init_out(未初始化)时生效。 - 如果
init_state==0(H),则H_score=0,否则-INF。 - 如果
init_state==2(I),则I_score=0,否则-INF。 - 如果
init_state==1(D),则D_score=0,否则-INF。
物理意义:这是 DP 矩阵左上角的初始化。根据 init_state 决定从哪个状态开始。
if(ref_in==NU_DONT_CARE) begin
H_score=-INF;
end
end
- 如果 ref 越界(
ref_in == NU_DONT_CARE = 5),H 分数设为 。 - 这是 X-Drop 剪枝的硬件实现:越界的格子不参与计算。
八、convergence_logic 组合逻辑
这是 TALCO 的核心,计算 convergence pointer。
always_comb begin : convergence_logic
if (ref_idx + query_idx == marker - 1) begin
CH_score = {ref_idx, 2'b11};
CI_score = {ref_idx, 2'b11}; // not required
CD_score = {ref_idx, 2'b11}; // not required
end
逐行解释:
ref_idx + query_idx == marker - 1:当前格子在 marker 前一格的反对角线上。CH_score = {ref_idx, 2'b11}:H 收敛指针 ={ref_idx, 2'b11},低 2 位11是特殊标记。CI_score和CD_score同理,但注释说不需要。
物理意义:这是 marker 前一格的特殊处理,用于标识 marker 边界。
else if (ref_idx + query_idx == marker) begin
CH_score = {ref_idx, 2'b00};
CI_score = {ref_idx, 2'b01};
CD_score = {ref_idx, 2'b10};
end
逐行解释:
ref_idx + query_idx == marker:当前格子在 marker 上。CH_score = {ref_idx, 2'b00}:H 收敛指针 = 自己,低 2 位00表示 H 状态。CI_score = {ref_idx, 2'b01}:I 收敛指针 = 自己,低 2 位01表示 I 状态。CD_score = {ref_idx, 2'b10}:D 收敛指针 = 自己,低 2 位10表示 D 状态。
物理意义:这是 marker 上的初始化。每个格子根据自己的状态,获得唯一 id {ref_idx, state}。这正是论文中"为 marker 上每个 cell 分配唯一 id"的硬件实现。
else begin
if (I_flag) begin
CI_score = CI_PE_prev;
end
else begin
CI_score = CH_PE_prev;
end
逐行解释:
- 非 marker 格子,正常传播。
- 如果
I_flag=1(从 I 延长 gap),CI_score = CI_PE_prev(继承左邻的 I 收敛指针)。 - 如果
I_flag=0(从 H 打开 gap),CI_score = CH_PE_prev(继承左邻的 H 收敛指针)。
物理意义:I 状态的回溯来源决定它继承哪个收敛指针。
if (D_flag) begin
CD_score = CD_score_reg;
end
else begin
CD_score = CH_score_reg;
end
逐行解释:
- 如果
D_flag=1(从 D 延长 gap),CD_score = CD_score_reg(继承上边的 D 收敛指针)。 - 如果
D_flag=0(从 H 打开 gap),CD_score = CH_score_reg(继承上边的 H 收敛指针)。
物理意义:D 状态的回溯来源决定它继承哪个收敛指针。
casex (dir)
4'b??00 : CH_score = CH_score_reg2;
4'b?001 : CH_score = CH_score_reg;
4'b?101 : CH_score = CD_score_reg;
4'b0?10 : CH_score = CH_PE_prev;
4'b1?10 : CH_score = CI_PE_prev;
default: CH_score = CH_score_reg2;
endcase
end
逐行解释:
casex使用通配符?,只匹配某些位。4'b??00:来自对角线 →CH_score = CH_score_reg2(继承对角线的 H 收敛指针)。4'b?001:来自水平 H →CH_score = CH_score_reg(继承左边的 H 收敛指针)。4'b?101:来自水平 D →CH_score = CD_score_reg(继承左边的 D 收敛指针)。4'b0?10:来自垂直 H →CH_score = CH_PE_prev(继承左边的 H 收敛指针)。4'b1?10:来自垂直 I →CH_score = CI_PE_prev(继承左边的 I 收敛指针)。default:默认继承对角线的 H 收敛指针。
物理意义:这正是论文中的 redirect 函数。根据 traceback 方向,继承前驱格子的 convergence pointer。
if(ref_in==NU_DONT_CARE) begin
CH_score=CONV_DONT_CARE;
CD_score=CONV_DONT_CARE;
CI_score=CONV_DONT_CARE;
end
end
- 如果 ref 越界,所有收敛指针设为
CONV_DONT_CARE = 0。 - 这是为了在 reduction tree 中不参与收敛比较。
九、assign query_idx
assign query_idx = query_idx_in;
query_idx直接来自query_idx_in,没有寄存器延迟。- 这样
boundary_conditions和convergence_logic可以立即使用当前 query 坐标。
十、ref_out 寄存器
always_ff @ (posedge clk) begin
if(rst)
ref_out <= 0;
else
ref_out <= ref_in;
end
ref_out延迟一拍输出ref_in。- 这样下一个 PE 在下一周期收到当前 PE 的 ref 碱基。
- 这是 systolic 阵列的流水线对齐。
十一、主寄存器块 always_ff
always_ff @ (posedge clk) begin
if (rst) begin
sub_A <= 0;
sub_C <= 0;
sub_G <= 0;
sub_T <= 0;
sub_N <= 0;
gap_open <= 0;
gap_extend <= 0;
H_prev <= 0;
D_prev <= 0;
H_PE_prev2 <= 0;
CH_score_reg <= 0;
CD_score_reg <= 0;
CH_score_reg2 <= 0;
ref_idx <= 0;
init_out <= 0;
ref_idx_out <= 0;
query_idx_out <= 0;
I_PE <= -INF;
H_PE <= -INF;
D_PE <= -INF;
CH_PE <= 0;
CI_PE <= 0;
CD_PE <= 0;
dir_out <= 0;
end
复位分支:所有寄存器清零,H/I/D 初始化为 -INF。
else if (set_param) begin
sub_A <= sub_A_in;
sub_C <= sub_C_in;
sub_G <= sub_G_in;
sub_T <= sub_T_in;
sub_N <= sub_N_in;
gap_open <= gap_open_in;
gap_extend <= gap_extend_in;
H_PE<=-INF;
param_valid<=param_valid_in;
end
参数加载分支:在 set_param=1 时,把打分参数加载到寄存器。这是 SET_PARAM 状态下的操作。
else if(param_valid) begin
init_out <= init_in;
dir_out <= dir;
ref_idx <= ref_idx_in;
ref_idx_out <= ref_idx_in;
if (init_in) begin
正常运行时:
init_out <= init_in:初始化信号向后传递。dir_out <= dir:traceback pointer 输出。ref_idx <= ref_idx_in:更新 ref 坐标。ref_idx_out <= ref_idx_in:ref 坐标传给下一个 PE。- 如果
init_in=1(初始化),进入下面的分支。
H_prev <= H_score;
D_prev <= D_score;
H_PE_prev2 <= H_PE_prev;
CH_score_reg <= CH_score;
CD_score_reg <= CD_score;
CH_score_reg2 <= CH_PE_prev;
query_idx_out <= query_idx_in + 1;
I_PE <= I_score;
H_PE <= H_score;
D_PE <= D_score;
CH_PE <= CH_score;
CI_PE <= CI_score;
CD_PE <= CD_score;
end
初始化分支(init_in=1):
H_prev <= H_score:当前 H 存入H_prev,供下一行使用。D_prev <= D_score:当前 D 存入D_prev。H_PE_prev2 <= H_PE_prev:左邻 H 存入对角线寄存器。CH_score_reg <= CH_score:当前 H 收敛指针存入左边寄存器。CD_score_reg <= CD_score:当前 D 收敛指针存入上边寄存器。CH_score_reg2 <= CH_PE_prev:左邻 H 收敛指针存入对角线寄存器。query_idx_out <= query_idx_in + 1:query 坐标加 1 传给下一个 PE。I_PE <= I_score:I 分数输出。H_PE <= H_score:H 分数输出。D_PE <= D_score:D 分数输出。CH_PE <= CH_score:H 收敛指针输出。CI_PE <= CI_score:I 收敛指针输出。CD_PE <= CD_score:D 收敛指针输出。
else begin
H_PE_prev2 <= block ? (PE_IDX?-INF:diag_score): H_init_in;
CH_score_reg2 <= block ? (PE_IDX?CONV_DONT_CARE:diag_CH_score): CONV_DONT_CARE;
H_prev <= -INF;
D_prev <= -INF;
CH_score_reg<=CONV_DONT_CARE;
CD_score_reg<=CONV_DONT_CARE;
CH_PE <= CONV_DONT_CARE;
CI_PE <= CONV_DONT_CARE;
CD_PE <= CONV_DONT_CARE;
end
end
end
非初始化分支(init_in=0):
H_PE_prev2 <= block ? (PE_IDX ? -INF : diag_score) : H_init_in:- 如果是后续 block,且 PE_IDX=0,用
diag_score(上一个 block 的分数);其他 PE 用-INF。 - 如果是第一个 block,用
H_init_in。
- 如果是后续 block,且 PE_IDX=0,用
CH_score_reg2 <= block ? (PE_IDX ? CONV_DONT_CARE : diag_CH_score) : CONV_DONT_CARE:- 类似,后续 block 的 PE[0] 用
diag_CH_score。
- 类似,后续 block 的 PE[0] 用
H_prev <= -INF:H 清零。D_prev <= -INF:D 清零。CH_score_reg <= CONV_DONT_CARE:收敛指针清零。CD_score_reg <= CONV_DONT_CARE。CH_PE <= CONV_DONT_CARE。CI_PE <= CONV_DONT_CARE。CD_PE <= CONV_DONT_CARE。
物理意义:非初始化时,PE 处于空闲状态,所有输出清零,等待下一个 block 或 tile 的初始化信号。
十二、完整的数据流总结
┌─────────────────────────────────────────────────────────────┐
│ 输入 │
│ ref_in, sub_*, gap_*, H_PE_prev, I_PE_prev, │
│ H_prev, D_prev, H_PE_prev2, CH_PE_prev, CI_PE_prev, │
│ CH_score_reg, CD_score_reg, CH_score_reg2, init_in │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ match_reward_calculation (组合逻辑) │
│ match_reward = 查表(ref_in, sub_A/C/G/T/N) │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ boundary_conditions (组合逻辑) │
│ H_PE_prev2_reg = (原点) ? 0 : H_PE_prev2 │
│ H_prev_reg = (init_in) ? H_init_in : H_prev │
│ D_prev_reg = (init_in) ? D_init_in : D_prev │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ score_calculation (组合逻辑) │
│ I_score = max(H_PE_prev + go, I_PE_prev + ge) │
│ D_score = max(H_prev + go, D_prev + ge) │
│ H_score = max(H_PE_prev2 + match, D_score, I_score) │
│ dir[3:0] = 方向编码 │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ convergence_logic (组合逻辑) │
│ marker 上:CH = {ref_idx, 00}, CI = {ref_idx, 01}, ... │
│ 非 marker:根据 dir 继承前驱的收敛指针 │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ always_ff (时钟上升沿) │
│ 更新所有输出寄存器和内部寄存器 │
└─────────────────────────────────────────────────────────────┘
十三、与论文的精确对应
| 论文公式/概念 | PE 代码行 |
|---|---|
HV_score, IV_score, I_score, I_flag | |
HH_score, DH_score, D_score, D_flag | |
HD_score, H_score, dir[1:0] | |
| 4-bit traceback pointer | dir[3:0] |
| Marker 初始化 | ref_idx + query_idx == marker 分支 |
| Redirect 函数 | casex(dir) 继承前驱收敛指针 |
| X-Drop 剪枝 | ref_in == NU_DONT_CARE → H_score = -INF |
| Convergence pointer | CH_PE, CI_PE, CD_PE |
| Block 衔接 | block ? diag_score : H_init_in |
| 原点初始化 | !query_idx && !ref_idx → H_PE_prev2_reg = 0 |
十四、关键设计细节
14.1 为什么 H_PE_prev2 需要延迟一拍?
在 systolic 阵列中,对角线来源的 H 分数需要跨两个 PE 传递。H_PE_prev2 是一个寄存器,存储"上一周期左邻 PE 的 H",这样当前 PE 在计算时,H_PE_prev2_reg 正好是对角线格子的 H。
14.2 为什么 CH_score_reg2 存的是 CH_PE_prev?
对角线来源的收敛指针需要跨两个 PE。CH_score_reg2 <= CH_PE_prev 表示"上一周期左邻 PE 的 CH",正好对应对角线格子。
14.3 为什么用 casex 而不是 case?
casex 允许 ? 通配符,可以只匹配 dir 的部分位。例如 4'b??00 匹配所有低 2 位为 00 的情况,不管高 2 位是什么。
14.4 为什么 init_in 是脉冲?
init_in 是一个单周期脉冲,表示"当前格子是 tile 的起点"。PE 只在 init_in=1 时更新分数输出,其他时候保持清零。
14.5 为什么 block 影响 H_PE_prev2?
当 block=1(后续 block)时,PE[0] 的对角线分数来自 diag_score(上一个 block 的最后一行),其他 PE 用 -INF。这是因为 block 衔接时,只有 PE[0] 需要从上一个 block 继承分数。
十五、一句话总结
PE 是 TALCO-XDrop 的基本计算单元,它在一个周期内完成:匹配分数查表、H/I/D 递推计算、4-bit traceback pointer 生成、convergence pointer 传播、X-Drop 剪枝,以及所有寄存器更新。PE 之间通过 systolic 方式连接,ref 和分数向右传递,每个 PE 绑定一个 query 碱基。PE 的设计精确对应论文 Algorithm 1 中的 AlignScore 和 redirect 函数。