DESIGN TRACK · 第 14 章

用 SystemVerilog 实现 RTL

把架构写成可综合、可维护、时序明确的硬件

建议用时:8-12 小时本章目标:完成流式累加器的 RTL,并让 lint 工具在零严重告警下通过。重点不是“代码能跑”,而是每一拍的硬件含义都明确。

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 告警。
  • 波形人工检查:正常批次、输入气泡、输出背压、复位中断任务。
通过标准:任意时刻暂停输入或输出都不会丢数据、重复数据或改变结果;复位后所有外部状态回到规格值。

工具与延伸资料

← 第 13 章:从需求到芯片架构