RTL 描述的是电路
always_ff 描述触发器,非阻塞赋值 <= 表示所有寄存器在时钟沿后一起更新;always_comb 描述组合逻辑,必须对所有输出在所有路径赋值,否则会推断锁存器。一个寄存器应只由一个时序块驱动。不要在可综合代码里使用任意延时、动态数组或依赖仿真调度顺序的写法。
always_ff @(posedge clk) begin
if (!rst_n) begin
count_q <= '0;
sum_q <= '0;
end else if (accept) begin
count_q <= count_q + 1'b1;
sum_q <= sum_q + {{8{1'b0}}, in_data};
end
end
assign accept = in_valid && in_ready;
位宽必须显式。无尺寸常量、符号数与无符号数混算、截断赋值都是常见隐患。把状态寄存器命名为 state_q,组合下一状态命名为 state_d,可以让评审者迅速区分当前拍与下一拍。
控制与数据通路分开
控制 FSM 只决定 IDLE、RUN、RESULT 三种状态及状态转换;数据通路负责计数和求和。IDLE 接受 start;RUN 在 accept 时累加,最后一个样本到达后进入 RESULT;RESULT 保持 out_valid 和 out_sum,直到 out_ready 完成握手再回到 IDLE。输出被背压时必须保持不变。
always_comb begin
state_d = state_q;
in_ready = 1'b0;
out_valid = 1'b0;
unique case (state_q)
IDLE: if (start_pulse) state_d = RUN;
RUN: begin
in_ready = 1'b1;
if (accept && last_sample) state_d = RESULT;
end
RESULT: begin
out_valid = 1'b1;
if (out_ready) state_d = IDLE;
end
endcase
end
这里要特别检查最后一个样本:输出应该包含本拍的 in_data,不能只锁存旧的 sum_q。可使用 sum_q + in_data 形成最终结果,或增加一个提交状态。
复位、跨时钟域与参数化
复位不是清空所有寄存器。只复位会影响外部可见行为和控制流的状态;大数据阵列通常通过有效位屏蔽。若必须跨时钟域,单比特电平用两级同步器,脉冲用握手或脉冲同步器,多比特数据用异步 FIFO。绝不能简单地给多位总线每一位各放两个触发器,因为各位可能在不同周期稳定。
参数化只用于真实变化的维度,例如 DATA_WIDTH 和 MAX_LENGTH。每个参数都应配合法性断言,避免“可配置”制造未经验证的状态空间。第一颗芯片宁可固定一个已验证配置,也不要追求无边界的通用性。
本章交付物
rtl/stream_accel.sv、寄存器块与顶层封装。- 模块级注释:时钟、复位、握手、参数范围,不复述显而易见的代码。
- Verilator lint 或同类检查零 error,逐条处置 width、latch、unused 告警。
- 波形人工检查:正常批次、输入气泡、输出背压、复位中断任务。
通过标准:任意时刻暂停输入或输出都不会丢数据、重复数据或改变结果;复位后所有外部状态回到规格值。
工具与延伸资料
- Verilator documentation:lint 与高性能 RTL 仿真。
- Yosys documentation:RTL 综合与网表处理。
- OpenROAD documentation:开放的 RTL-to-GDS 物理设计流程。
- SkyWater Open Source PDK:开放工艺设计规则与库资料。