FPGA/IC常用的Python库:如何利用python实现Verilog自动化生成

FPGA技术江湖 2026-09-20 08:36

 

前言

在编写 Verilog 的过程中,我们常常会碰到一些重复性的脏活累活:比如调整总线位宽、增加模块端口、扩展 case 分支、插入流水打拍等等。由于 Verilog 本身是硬件描述语言,很难像高级软件语言那样灵活抽象,所以大部分时候我们只能复制粘贴再手动修改参数,既繁琐又容易出错。这时候,如果借助 Python 的 Jinja2 模板引擎,就可以把这些机械性的代码生成工作自动化,让我们从重复劳动中解放出来。

什么是jinjia2

举个例子

假设你有一个很常见的模块,但每个实例的位宽 不一样。以前你可能复制粘贴代码然后手改,容易出错。

模板引擎的思路是:你写一个模板文件,里面把会变的地方用特殊标记标出来(就像填空题的空格),然后用 Python 脚本把实际数值填进去,自动生成最终的.v文件。

比如模板文件里写:

module {{ module_name }} (
    input
  wire        clk,
    input
  wire        rst_n,
    output
 reg  [{{ data_width-1 }}:0] rdata
);

然后你告诉 Python:module_name = "reg_slice32"data_width = 32,它就会生成:

module apb_reg_slice (
    input
  wire        clk,
    input
  wire        rst_n,
    output
 reg  [31:0] rdata
);

{{ }} 就是空,Python 把值填进去。

jinjia2和其他方法

有人可能会说:那我用 Python 的 f-string 或字符串拼接来生成不就行了?确实可以,但当逻辑变得复杂时,字符串拼接的代码会变得难以阅读和维护。比如你需要根据条件生成不同的代码块,或者循环生成多个实例,字符串拼接会让 Python 脚本里充满大量的 iffor 和换行符 \n,最后生成的 Verilog 结构隐藏在层层字符串中,可读性极差。

Jinja2 模板引擎解决了这个问题:它将 模板(接近最终 Verilog 的文本,带占位符和控制标签)与 数据(从 Excel、JSON 或自定义对象中提取的参数)分离。硬件工程师可以直接编辑模板文件,专注于 RTL 结构,而 Python 脚本只负责准备数据和调用渲染。两者各司其职,互不干扰。

快速开始

安装

Jinja2 是 Python 的一个第三方库,安装非常简单:

pip install jinja2

核心语法

Jinja2 的模板就是普通文本文件(比如后缀为 .v.j2),里面可以包含三种特殊标记:

  1. 1. {{ 变量 }} :插值表达式,用于输出变量的值。
  2. 2. {% 控制语句 %} :如 iffor 等,用于控制模板的逻辑。
  3. 3. {# 注释 #} :模板注释,渲染时会被忽略。

我们用几个例子来感受一下。

插值:填空

模板文件 example.v.j2

module {{ module_name }} (
    input
  wire [{{ width-1 }}:0] data_in,
    output
 wire [{{ width-1 }}:0] data_out
);
assign
 data_out = data_in;
endmodule

Python 脚本:

from jinja2 import Template

template = Template(open('example.v.j2').read())
verilog = template.render(module_name='pass_through', width=16)
print
(verilog)

输出:

module pass_through (
    input
  wire [15:0] data_in,
    output
 wire [15:0] data_out
);
assign
 data_out = data_in;
endmodule

注意 {{ width-1 }} 中的 width 是传入的变量,Jinja2 会先计算表达式 width - 1,再输出结果。如果 width 是字符串 "16",则需要先转换为整数,我们稍后会讲到过滤器。

条件判断:根据参数生成不同代码

模板:

{% if pipe_stages > 0 %}
    // 插入流水寄存器

    reg
 [31:0] pipe_reg [0:{{ pipe_stages-1 }}];
    always
 @(posedge clk) begin
        pipe_reg[0] <= data_in;
        for
 (int i=1; i<{{ pipe_stages }}; i++) begin
            pipe_reg[i] <= pipe_reg[i-1];
        end

    end

    assign
 data_out = pipe_reg[{{ pipe_stages-1 }}];
{% else %}
    assign
 data_out = data_in;
{% endif %}

当 pipe_stages = 3 时,渲染出三级流水;当 pipe_stages = 0 时,直接赋值。

循环:批量生成实例

模板:

{% for i in range(num_instances) %}
    sub_module u_sub_{{ i }} (
        .clk
  (clk),
        .in
   (data_in[{{ i }}]),
        .out
  (data_out[{{ i }}])
    );
{% endfor %}

num_instances = 4 时会生成 4 个实例,名称从 u_sub_0 到 u_sub_3

过滤器:数据预处理

过滤器用于修改变量的输出形式,语法是 {{ 变量 | 过滤器 }}。常用的有:

  • • int:将字符串转换为整数(因为从 Excel 或 JSON 读到的数字往往是字符串)。
  • • upper / lower:转换大小写。
  • • default(value):如果变量未定义或为空,则使用默认值。

例如:

localparam AW = {{ addr_width | int }};

如果 addr_width 是字符串 "12"int 过滤器会将其转换为整数 12,避免生成 localparam AW = "12"; 这样的非法代码。

使用宏(Macro)复用公共代码

当多个模块拥有相同的端口声明(例如 APB 从机接口)时,可以在模板中定义宏,类似于 Verilog 中的 `define 或函数。

在模板中定义宏:

{% macro apb_slave_ports() %}
    input
  wire        pclk,
    input
  wire        presetn,
    input
  wire [31:0] paddr,
    input
  wire        psel,
    input
  wire        penable,
    input
  wire        pwrite,
    input
  wire [31:0] pwdata,
    output
 wire [31:0] prdata,
    output
 wire        pready
{% endmacro %}

然后在任何模块中调用:

module my_slave (
    {{ apb_slave_ports() }}
);

这样所有 APB 从机的端口定义就统一了,修改宏即可批量更新所有模块。

jinjia2的应用

假设你负责一个模块,这个模块有十几个不同的配置寄存器和状态寄存器,有的可读写(RW),有的只读(RO),位宽各不相同,偏移地址也不同。手写它们的 APB 总线读写逻辑和端口不仅枯燥,而且极易复制粘贴出错。

我们可以用 Jinja2 这样重写这个例子

Python 脚本 (提供结构化数据)

在实际工程中,这些数据通常是从架构师写的 Excel 或 YAML 文件里读出来的,Python 将它们组织成列表和字典的结构:

from jinja2 import Template

# 寄存器配置表:包含了名字、偏移地址、位宽和读写类型

registers = [
    {"name": "MAC_CTRL",   "offset": "0x00", "width": 32, "type": "RW"},
    {"name": "LINK_STAT",  "offset": "0x04", "width": 1,  "type": "RO"},
    {"name": "RX_PKT_CNT", "offset": "0x08", "width": 32, "type": "RO"},
    {"name": "INT_EN",     "offset": "0x0C", "width": 16, "type": "RW"}
]

# 读取 Jinja2 模板文件并渲染

with
 open("csr_template.v.j2", "r") as f:
    template = Template(f.read())

rendered_rtl = template.render(module_name="mac_csr", regs=registers)
print
(rendered_rtl)

Jinja2 模板 (csr_template.v.j2)

在这里你可以看到 Jinja2 的强大:{% for %} 循环生成端口和逻辑,{% if %} 区分 RW 和 RO,甚至是过滤器 |lower 自动把大写宏转成小写信号名。

module {{ module_name }} (
    input
  wire        clk,
    input
  wire        rst_n,
    
    // 简化的 APB 总线接口

    input
  wire [31:0] paddr,
    input
  wire        pwrite,
    input
  wire        psel,
    input
  wire [31:0] pwdata,
    output
 reg  [31:0] prdata,

    // 自动生成的硬件交互端口

{% for reg in regs %}
  {% if reg.type == 'RW' %}
    output
 reg  [{{ reg.width-1 }}:0] {{ reg.name|lower }}_q{% if not loop.last %},{% endif %}
  {% elif reg.type == 'RO' %}
    input
  wire [{{ reg.width-1 }}:0] {{ reg.name|lower }}_d{% if not loop.last %},{% endif %}
  {% endif %}
{% endfor %}
);

    // --- 自动生成的写逻辑 (仅限 RW 寄存器) ---

    always
 @(posedge clk or negedge rst_n) begin
        if
 (!rst_n) begin
{% for reg in regs if reg.type == 'RW' %}
            {{ reg.name|lower }}_q <= {{ reg.width }}'h0;
{% endfor %}
        end
 else if (psel && pwrite) begin
            case
 (paddr[7:0])
{% for reg in regs if reg.type == 'RW' %}
                8
'h{{ reg.offset[-2:] }}: {{ reg.name|lower }}_q <= pwdata[{{ reg.width-1 }}:0];
{% endfor %}
            endcase

        end

    end


    // --- 自动生成的读逻辑 (包含 RW 和 RO) ---

    always
 @(*) begin
        prdata = 32'h0; // 默认值
        if
 (psel && !pwrite) begin
            case
 (paddr[7:0])
{% for reg in regs %}
                8
'h{{ reg.offset[-2:] }}: prdata = {{ "{32'h0, " if reg.width < 32 else "" }}{{ reg.name|lower }}_{{ "q" if reg.type == "RW" else "d" }}{{ "}" if reg.width < 32 else "" }};
{% endfor %}
            endcase

        end

    end


endmodule

生成的 Verilog 结果

执行 Python 后,模板引擎一下子就给你吐出了可以直接拿去综合的、完全不出错的代码:

module mac_csr (
    input
  wire        clk,
    input
  wire        rst_n,
    
    // 简化的 APB 总线接口

    input
  wire [31:0] paddr,
    input
  wire        pwrite,
    input
  wire        psel,
    input
  wire [31:0] pwdata,
    output
 reg  [31:0] prdata,

    // 自动生成的硬件交互端口

    output
 reg  [31:0] mac_ctrl_q,
    input
  wire [0:0]  link_stat_d,
    input
  wire [31:0] rx_pkt_cnt_d,
    output
 reg  [15:0] int_en_q
);

    // --- 自动生成的写逻辑 (仅限 RW 寄存器) ---

    always
 @(posedge clk or negedge rst_n) begin
        if
 (!rst_n) begin
            mac_ctrl_q <= 32'h0;
            int_en_q <= 16'h0;
        end
 else if (psel && pwrite) begin
            case
 (paddr[7:0])
                8'h00
: mac_ctrl_q <= pwdata[31:0];
                8'h0C
: int_en_q <= pwdata[15:0];
            endcase

        end

    end


    // --- 自动生成的读逻辑 (包含 RW 和 RO) ---

    always
 @(*) begin
        prdata = 32'h0; // 默认值
        if
 (psel && !pwrite) begin
            case
 (paddr[7:0])
                8'h00
: prdata = mac_ctrl_q;
                8'h04
: prdata = {32'h0, link_stat_d};
                8'h08
: prdata = rx_pkt_cnt_d;
                8'h0C
: prdata = {32'h0, int_en_q};
            endcase

        end

    end


endmodule

写在最后

Jinja2 模板引擎为 Verilog 代码生成提供了一种优雅的解决方案。它将硬件设计逻辑(模板)与数据准备逻辑(Python)解耦,使得代码生成过程清晰、可维护、可扩展。无论是调整总线位宽、增加流水级,还是批量实例化模块,都可以通过修改模板轻松完成,而无需在 Python 脚本中堆砌复杂的字符串拼接。


- -THE END- -


往期精选 

FPGA/IC常用的Python库:如何利用python实现Verilog自动化生成图1 
FPGA/IC常用的Python库:如何利用python实现Verilog自动化生成图2 

FPGA/IC常用的Python库:如何利用python实现Verilog自动化生成图3
FPGA/IC常用的Python库:如何利用python实现Verilog自动化生成图4

FPGA技术江湖广发江湖帖

无广告纯净模式,给技术交流一片净土,从初学小白到行业精英业界大佬等,从军工领域到民用企业等,从通信、图像处理到人工智能等各个方向应有尽有,QQ微信双选,FPGA技术江湖打造最纯净最专业的技术交流学习平台。


FPGA技术江湖微信交流群

FPGA/IC常用的Python库:如何利用python实现Verilog自动化生成图5

加群主微信,备注姓名+学校/公司+专业/岗位进群


FPGA技术江湖QQ交流群

FPGA/IC常用的Python库:如何利用python实现Verilog自动化生成图6

备注姓名+学校/公司+专业/岗位进群

FPGA/IC常用的Python库:如何利用python实现Verilog自动化生成图7

关于科技区角:国内科技展会垂直内容策划服务商,提供从论坛内容全案策划、会展市场化IP打造到精准专业观众一站式邀约服务,以产业内容吸引高质量B端人群,打通展会从议题设计、演讲嘉宾邀约、宣传预热、精准邀观到供需对接全链路。
声明:内容取材于网络,仅代表作者观点,如有内容违规问题,请联系处理。
FPGA IC 自动化
more
人形机器人进入万元时代,自动化厂商杀入供应链上游
115亿!工业自动化超重磅融资!
FPGA/IC常用的Python库:如何利用python实现Verilog自动化生成
工业自动化的未来展望:AI与网络安全
行业财报 | 日立、西门子、施耐德电气、中国中车、ABB集团、江森自控等44家工业电气自动化企业2026年第二季度和上半年业绩汇总
聚势越南河内,凯傲集团携自动化内部物流方案加速布局东南亚市场
厦门量子堆栈科技13.5天长假引热议,极客团队以AI自动化支撑“反内卷”福利
2026 H1自动化整体市场增长4.2%:OEM领涨,流程行业承压
OpenAI公开4+10条保命指南,专防AI自动化攻击
Adobe“吞下”印度初创Rilo,营销自动化赛道再掀整合潮
Copyright © 2025-成都区角科技有限公司
蜀ICP备2025143415号-1
  
川公网安备51015602001305号