引言

你已经了解RTL代码的仿真过程及其原理

  • 但仿真只是用程序来模拟电路的行为
  • 要将电路制造出来, 还需要经过很多步骤

 

本次课内容:

  • 综合器如何将RTL代码转换为网表
    • 解析 -> 细化 -> 粗粒度综合 -> 细粒度综合 -> 工艺映射 -> 网表生成

从RTL代码到可流片版图(简略版)

RTL代码并不足够

RTL代码是从逻辑上描述电路的行为

  • 但要将电路生产制造出来, 晶圆厂还需要更多物理层面的信息

 

具体地, 晶圆厂需要的是一个GDSII(Graphic Design System II)格式的版图文件

  • 版图文件描述了电路中每个元素的物理位置
    • 在坐标(3, 4)的位置有一个与门
    • 在坐标(4, 2)(0, 2)之间有一根走线

 

要将RTL代码转换成类似的GDS版图, 则需要一系列EDA工具进行多个阶段的处理

  • 需要通过布局阶段确定每一个门电路的坐标
  • 需要通过布线阶段确定如何通过线网将门电路连接起来

PDK和标准单元

通常, 晶圆厂会提供一个工艺设计套件(Process Design Kit, 简称PDK)

  • 包含特定工艺节点下的器件模型, 设计规则, 工艺约束, 验证文件和标准单元库等一系列资源
  • 物理设计工程师通常使用PDK来设计出符合晶圆厂制造规范的电路

 

PDK中的标准单元库(standard cell library)包含了该工艺所支持的逻辑单元, 称为标准单元(standard cell)

  • 一些常见的逻辑单元: 反相器, 二输入与非门, 三输入或门, 触发器……

 

  • 标准单元是GDS版图文件中描述的一部分对象
  • 也是EDA工具进行处理的最小单元

逻辑综合 = 将RTL代码转换为标准单元

人工将RTL代码转换为标准单元是很繁琐的

  • 通常由综合器(也称综合工具)来进行这一转换工作

 

综合器的输入是RTL代码, 输出是标准单元的网表(netlist)

  • 网表不仅记录了转换之后的标准单元
  • 还记录了标准单元之间的连接关系(即拓扑结构)

 

网表所描述的电路(包括标准单元及其连接关系), 应该和RTL代码所描述的电路在逻辑上等价

 

EDA工具会在后续阶段基于网表进行物理设计环节

  • 最终将标准单元及其连接关系传递到GDS版图中

逻辑综合

逻辑综合包含很多阶段

“一生一芯”提供了一个基于开源EDA的综合和评估项目yosys-sta

  • 可以体验综合的过程, 并查看综合生成的网表

 

综合包含较多阶段(以yosys为例)

  • 解析 -> 细化 -> 粗粒度综合 -> 细粒度综合 -> 工艺映射 -> 网表生成

 

借助开源综合工具yosys, 我们来看看每一个阶段都在做什么

// counter.v
module counter(
  input clk,
  input rst,
  input en,
  output reg [1:0] count
);
  always @(posedge clk) begin
    if (rst) count <= 2'd0;
    else if (en) count <= count + 2'd1;
  end
endmodule

解析文件(Parsing)

$ yosys counter.v

yosys读入counter.v, 并进行解析, 然后将其转换为抽象语法树(AST)

  • 这个过程和C语言的编译非常类似, 包含词法分析和语法分析
    • 尝试去掉源文件中的其中一个分号;

细化(Elaboration)

细化阶段的工作包括

  • 解析模块间的实例化关系
  • 计算模块实例的参数
  • 完成模块实例化的实例名和端口绑定
  • ……

 

yosys> hierarchy -check -top counter

hierarchy命令需要指定一个顶层模块

  • yosys将以这个顶层模块为起点, 依次展开所有实例化的子模块, 从而确定整个设计的边界
    • 未被实例化的模块将被删除
  • 并将整个设计的AST转换成yosys的一种中间语言RTLIL
    • 和C语言编译中的中间代码生成阶段非常相似

细化(Elaboration) - 语义分析

猜想: 在yosys的细化阶段中还进行了类似C语言编译中语义分析的工作

 

一些尝试:

  • posedge clk改为posedge count
  • 添加一条模块实例化语句mymodule abc(clk, rst);

 

上述两种错误都符合Verilog的语法, 因此无法在解析文件阶段发现

细化(Elaboration) - 中间代码生成

将当前设计的RTLIL以文本形式输出到终端:

yosys> dump

将RTLIL输出到文件:

yosys> write_rtlil counter.rtlil
autoidx 3
attribute \hdlname "counter"
attribute \top 1
attribute \src "counter.v:2.1-12.10"
module \counter
  wire width 2 $0\count[1:0]
  wire width 2 $add$counter.v:10$2_Y
  wire input 1 \clk
  wire width 2 output 4 \count
  wire input 3 \en
  wire input 2 \rst
  cell $add $add$counter.v:10$2
    parameter \A_SIGNED 0
    parameter \A_WIDTH 2
    parameter \B_SIGNED 0
    parameter \B_WIDTH 2
    parameter \Y_WIDTH 2
    connect \A \count
    connect \B 2'01
    connect \Y $add$counter.v:10$2_Y
  end
  process $proc$counter.v:8$1
    assign $0\count[1:0] \count
    switch \rst
      case 1'1
        assign $0\count[1:0] 2'00
      case
        switch \en
          case 1'1
            assign $0\count[1:0] $add$counter.v:10$2_Y
          case
        end
    end
    sync posedge \clk
      update \count $0\count[1:0]
  end
end

RTLIL的一些说明

  • attribute用于标识一些属性
    • 例如, attribute \src "counter.v:10.27-10.39"用于标识相应的元素在源文件中的位置
  • wire width 2 $0\count[1:0]表示定义一个位宽为2的信号, 其名称为$0\count[1:0] ($, \, [, :]这些字符都是名称的一部分)
  • cell $add $add$counter.v:10$2表示实例化一个类型为$add的单元, 其名称为$add$counter.v:10$2
    • 单元的具体参数用parameter表示
      • parameter \A_WIDTH 2表示单元的端口A的位宽为2
      • parameter \A_SIGNED 0表示单元的端口A是无符号的
    • 端口的连接关系用connect表示
      • connect \Y $add$counter.v:10$2_Y表示单元的端口Y与信号$add$counter.v:10$2_Y相连

RTLIL的一些说明(2)

  • process表示一个行为描述过程, 其中
    • assign表示信号的赋值
    • switch-case表示根据信号的值进行条件性赋值
    • sync表示当条件满足时对信号进行更新

 

RTLIL的语法虽然和Verilog不同, 但也是在描述硬件, 而且更接近网表

 

将RTLIL中的拓扑关系通过结构图的方式进行可视化:

yosys> show

粗粒度综合(Coarse-grain synthesis)

基于设计的粗粒度表示进行处理

  • 粗粒度表示 = 采用字级单元(word-level cells)来描述设计
  • 字级单元支持多位宽和参数功能, 名称以$为前缀
    • 一元运算 - 如$neg, $not, $reduce_or
    • 二元运算 - 如$add, $eq, $mul, $shift
    • 数据选择 - 如$bmux, $mux, $pmux
    • 寄存器 - 如$dff, $dffe, $adff
    • ……

粗粒度综合的工作主要包含:

  • 将过程描述转换为粗粒度表示 - proc
  • 优化 - opt
  • 有限状态机的识别和处理 - fsm
  • 存储器的识别和处理 - memory

细粒度综合(Fine-grain synthesis)

基于设计的细粒度表示进行处理

  • 细粒度表示 = 采用门级单元(gate-level cells)来描述设计

 

  • 门级单元的数据位宽都是1, 不提供参数功能, 以$_XXX_的形式命名
    • 简单组合逻辑单元 - 如$_AND_, $_NOR_, $_XOR_
    • 复杂组合逻辑单元 - 如$_AOI3_, $_MUX4_, $_OAI4_
    • 触发器单元 - 如$_DFF_P_, $_DFF_NP0_, $_DFFE_PP1N_
    • 锁存器单元 - 如$_DLATCH_P_, $_DLATCHSR_PPP_

 

粗粒度综合的工作主要包含:

  • 将粗粒度表示转换为细粒度表示 - techmap
  • 拆分多位的线网和端口 - splitnets -ports
  • 优化 - opt -full

工艺映射(Technology mapping)

工艺映射 = 从工艺无关的电路表示映射到具体工艺的实现

  • 将设计的细粒度表示映射到目标工艺的标准单元

 

标准单元库的示例(来源于yosys手册)

  • 以文本方式描述了标准单元的属性
    • 单元的面积(area), 一般以\(um^2\)为单位
    • 端口(pin), 包含方向(direction)
      • 特别地, 输出端口还包含功能(function)属性, 通过逻辑表达式给出
      • 对于触发器的时钟输入端口, 还包含时钟(clock)属性, 用于标识该端口为时钟信号
library(demo) {
  cell(BUF) {
    area: 6;
    pin(A) { direction: input; }
    pin(Y) { direction: output;
              function: "A"; }
  }
  cell(NOT) {
    area: 3;
    pin(A) { direction: input; }
    pin(Y) { direction: output;
              function: "A'"; }
  }
  cell(NAND) {
    area: 4;
    pin(A) { direction: input; }
    pin(B) { direction: input; }
    pin(Y) { direction: output;
             function: "(A*B)'"; }
  }
  cell(NOR) {
    area: 4;
    pin(A) { direction: input; }
    pin(B) { direction: input; }
    pin(Y) { direction: output;
             function: "(A+B)'"; }
  }
  cell(DFF) {
    area: 18;
    ff(IQ, IQN) { clocked_on: C;
                  next_state: D; }
    pin(C) { direction: input;
                 clock: true; }
    pin(D) { direction: input; }
    pin(Q) { direction: output;
              function: "IQ"; }
  }
}

工艺映射(2)

对时序逻辑单元进行工艺映射:

yosys> dfflibmap -liberty cell.lib

门级单元$_SDFFE_PP0P_被替换成DFF和一些$_MUX_

  • 其中DFF就是标准单元库中的标准单元
  • 标准单元库中没有与$_SDFFE_PP0P_功能完全相同的标准单元
    • $_SDFFE_PP0P_ = 带高有效同步复位信号和高有效使能信号的正边沿D触发器
    • 需要引入额外的组合逻辑单元来实现高有效同步复位高有效使能

修复show命令对DFF的显示问题:

yosys> read_liberty -lib cell.lib

对组合逻辑单元进行工艺映射:

yosys> abc -liberty cell.lib

所有门级单元都被替换成标准单元

网表生成

将网表写入到文件:

yosys> write_verilog netlist.v

 

输出统计报告:

yosys> stat -liberty cell.lib

使用综合脚本

# yosys.tcl

yosys -import
read_verilog counter.v
hierarchy -check -top counter
yosys proc
opt
fsm
memory
techmap
splitnets -ports
opt -full
dfflibmap -liberty cell.lib
abc -liberty cell.lib
write_verilog netlist.v
stat -liberty cell.lib
yosys yosys.tcl

细说粗粒度综合

将过程描述转换为粗粒度表示

目前的RTLIL还存在process这样的过程描述(结构图中的PROC节点)

  • 还不属于粗粒度表示, 无法对其开展粗粒度表示相关的处理工作

 

将所有过程描述转换为粗粒度表示:

yosys> proc

proc命令是一条宏命令, 包含一系列子命令:

  1. proc_clean - 移除空分支和空的过程描述
  2. proc_rmdead - 移除不可达的case分支
  3. proc_prune - 移除冗余的赋值操作(被后续赋值操作所覆盖)
  4. proc_init - 将过程描述中的init操作转换为相应信号的init属性
  5. proc_arst - 识别异步复位
  6. proc_rom - 将过程描述中的switch操作在适合时转换为ROM
  7. proc_mux - 将过程描述中的switch操作转换为$mux单元

将过程描述转换为粗粒度表示(2)

proc命令是一条宏命令, 包含一系列子命令:

  1. proc_dlatch - 将过程描述中的锁存器转换为D锁存器类型的单元
  2. proc_dff - 将过程描述中的触发器转换为D触发器类型的单元
  3. proc_memwr - 将过程描述中的存储器写操作转换为$memwr单元
  4. proc_clean - 移除空分支和空的过程描述
  5. opt_expr -keepdc - 进行表达式相关的优化

 

一些子命令的行为和C语言的编译优化技术类似:

  • proc_clean, proc_rmdead, proc_prune, opt_expr

 

剩余的关键工作:

  • 将RTLIL中过程描述的switch-case部分转换为$mux单元
  • sync描述转换为D锁存器类型或D触发器类型的单元

综合过程中的优化

和编译优化类似, 综合器一般也提供优化的功能

opt命令是一条宏命令, 包含一系列子命令:

  1. opt_expr - 常量合并和简单表达式改写
  2. opt_merge -nomux - 合并相同的单元, 但不合并选择器类型的单元
  3. 开始循环:
  4. opt_muxtree - 移除嵌套选择器中的不可达分支
  5. opt_reduce - 简化多输入的选择器, 与门和或门
  6. opt_merge - 合并相同的单元
  7. opt_share - 合并输入相同, 类型相同, 且不会同时激活的单元
  8. opt_dff - D触发器的常量优化和时钟复位信号合并
  9. opt_clean - 移除无用的单元和线网
  10. opt_expr - 常量合并和简单表达式改写
  11. 若设计发生变化, 则跳转到第3步继续循环

综合优化技术

为了方便理解, 我们用Verilog代码来呈现优化前后的语义

  • 常量合并和简单表达式改写(opt_expr) - 在一些表达式中, 若某输入为特定的常量, 或表达式符合某种特殊样式, 可对其进行简化
//          优化前              |            优化后
  wire a, b, c, x, y, z;       |    wire a, b, c, x, y, z;
  // ......                    |    // ......
  assign x = a != a;           |    assign x = 1'0;
  assign y = b | x;            |    assign y = b;
  assign z = c == x;           |    assign z = ~c;

 

  • 合并相同单元(opt_merge) - 对于多个功能和输入都相同的单元, 可将其合并成一个单元, 让其输出驱动原来的各输出信号, 从而减少单元的数量
//          优化前              |            优化后
  wire a, b, x, y;             |    wire a, b, x, y;
  // ......                    |    // ......
  assign x = a + b;            |    assign x = a + b;
  assign y = b + a;            |    assign y = x;

综合优化技术(2)

  • 移除嵌套选择器中的不可达分支(opt_muxtree) - 在嵌套的选择器中, 某些分支因条件冲突而不可达, 可将其移除
//          优化前                 |         优化后
  wire a, b, c, d, x;             |    wire a, b, c, d, x;
  // ......                       |    // ......
  assign x = a ? (a ? b : c) : d; |    assign x = a ? b : d;

 

  • 简化多输入的选择器, 与门和或门(opt_reduce) - 对于多输入的选择器, 与门和或门, 它们中有一些输入可能相同, 可以对这些输入进行消除或合并
//          优化前                 |         优化后
  wire [31:0] inst, imm;          |    wire [31:0] inst, imm;
  wire sel, x;                    |    wire sel, x;
  // ......                       |    // ......
  assign imm = !sel ? 32'b0 :     |    assign imm[11:0] = !sel ? 12'b0 : inst[31:20];
    {{20{inst[31]}}, inst[31:20]};|    assign imm[31:12] = {20{imm[11]}};
  assign x = &imm;                |    assign x = &imm[11:0];

综合优化技术(3)

  • D触发器的常量优化(opt_dff) - 若D触发器的数据输入端为常量, 则可将其替换为常量, 从而移除相应的D触发器单元
//          优化前              |            优化后
  reg [31:0] r;                   |    wire [31:0] r;
  // ......                       |    // ......
  always @ (posedge clk)          |    assign r = 32'hdeadbeef;
    r <= 32'hdeadbeef;            |

 

  • 移除无用的单元和线网(opt_clean) - 如果某些单元和线网不影响模块的输出, 可将其移除
//          优化前              |            优化后
  module m(                       |    module m(
    input a, b;                   |      input a, b;
    output x;                     |      output x;
  );                              |    );
    wire t;                       |      assign x = a + b;
    assign x = a + b;             |    endmodule
    assign t = a & b;             |
  endmodule                       |

其他优化技术包括位宽削减, 窥孔优化等, 可查阅yosys文档或相关资料

有限状态机的识别和处理

例: 用有限状态机(Finite State Machine, FSM)识别输入中的连续3个1

含义 输入0 输入1
S0 初始状态 S0/0 S1/0
S1 识别了1个1 S0/0 S2/0
S2 识别了2个1 S0/0 S2/1

对于更复杂的FSM, 存在可以优化的机会:

  • 冗余状态, 可合并的状态/输入/输出
  • 但在运算符类别的字级单元上很难发现这些优化的机会

思路:

  1. 综合器先在字级单元上识别出FSM
  2. 在FSM的语义上对其进行分析和优化
  3. 将优化后的FSM映射回字级单元

有限状态机的识别和处理(2)

识别并优化FSM:

yosys> fsm

fsm命令是一条宏命令, 包含一系列子命令:

  1. fsm_detect - FSM检测, 根据一定的规则在RTLIL中识别出FSM, 并用特殊属性标记相关单元
  2. fsm_extract - FSM抽取, 将标记的相关单元用$fsm单元替代, 并解析出状态转移表
  3. fsm_opt - FSM优化, 根据状态转移表对FSM进行优化
    • 包括移除无用的输出信号, 合并上游相同的输入信号, 合并相同状态下输出相同的不同输入, 根据常量输入简化状态等
  4. fsm_recode - FSM重编码, 使用独热码对状态信号进行重新编码
  5. fsm_map - 单元映射, 将处理后的$fsm单元映射回字级单元

有限状态机的识别和处理(3)

一个示例

module top (
  input clk, reset, x,
  output y
);
  reg [1:0] s;
  always @(posedge clk) begin
    if (reset) s <= 2'b0;
    else begin
      case (s)
        2'b00: s <= (x ? 2'b01 : 2'b00);
        2'b01: s <= (x ? 2'b10 : 2'b00);
        2'b10: s <= (x ? 2'b11 : 2'b00);
        2'b11: s <= (x ? 2'b11 : 2'b00);
      endcase
    end
  end
  assign y = ((s == 2'b10) || (s == 2'b11)) && x;
endmodule
# yosys.tcl
yosys -import
read_verilog -sv top.v
hierarchy -top top
yosys proc
opt -nodffe -nosdff
stat

fsm_detect
fsm_extract
fsm_opt
opt_clean
fsm_opt
fsm_recode
fsm_info
fsm_map

opt
stat

存储器的识别和处理

另一种需要特殊处理的单元是存储器

 

识别并优化存储器:

yosys> memory

 

memory命令是一条宏命令, 包含一系列子命令:

  • 将上下游的触发器合并到存储器的读写单元
  • 将存储器的多个读写单元合并成一个多端口的存储器单元
  • ……

FPGA和ASIC流程中的存储器

FPGA侧重灵活的可编程特性

  • 综合器根据RTL代码动态识别存储器规格, 实现存储器器件的按需分配
  • 但由于存储器器件需要支持可编程功能, 其性能不高(如最大工作频率)

 

ASIC追求更高的性能

  • 存储器器件不可编程, 由标准单元库提供若干种规格确定的存储器
  • RTL开发者根据设计目标自行选择, 在RTL代码中实例化
    • 1个64x64的存储器单元 - 总面积较小, 但读延迟可能较高
    • 2个32x64的存储器单元进行拼接 - 总面积较大, 但读延迟可能较优
  • 不同的存储器规格具有不同的形状, 也会对后续的布局布线产生影响
  • 综合器难以自动进行存储器的识别和映射, 灵活性较弱

 

将来再深入讨论存储器的问题

总结

逻辑综合 = 从RTL代码到网表

  • PDK和标准单元
    • 物理设计工程师用它们设计出符合晶圆厂制造规范的电路
    • 标准单元是EDA工具进行处理的最小单元
    • 逻辑综合 = 将RTL代码转换为标准单元(的网表)

 

  • 逻辑综合 = 解析 -> 细化 -> 粗粒度综合 -> 细粒度综合 -> 工艺映射 -> 网表生成
    • 通过yosys了解每一步的变化

 

  • 了解常见的综合优化技术
    • 常量合并和简单表达式改写, 合并相同单元, 移除嵌套选择器中的不可达分支, 简化多输入的选择器/与门/或门, D触发器的常量优化, 移除无用的单元和线网