如何使用Python实现FPGA编程“自动化”

如何使用Python实现FPGA编程“自动化”

之前读到过一个公众号文章,写了关于《使用Python实现Vivado和Modelsim仿真的自动化》,连接https://mp.weixin.qq.com/s/2YR_LjpQNtJr9beqnCz7CA。根据该文章,基于自己的编程习惯和工作需要,我做了一些修改和便于自己编程的一些python代码,这里和大家介绍一下。


文章目录

  • 如何使用Python实现FPGA编程“自动化”
  • 一、自动生成tb文件
    • 1、对于被生成的模块的端口格式,必须按照以下格式,才能自动生成。
    • 2、预定义字符串列表,用于生成tb文件的基本框架,如下:
    • 3、读取需要仿真的模块顶层文件,读取到环境列表中
    • 4、从vhdfilelines中循环查找端口、模块名称
    • 5、提取要仿真的模块端口信息
    • 6、修改实体、结构体部分的模块名称,如下:
    • 7、生成tb文件中调试模块的信号定义
    • 8、生成测试实体说明部分
    • 9、生成测试实体的例化部分信号连接
    • 10、将修改好的激励文件写入到对应的tb文件中
  • 二、对于vivado使用modelsim仿真时修改代码不退出重新加载的方法
    • 1、修改修改xxxxxxxx_Compile.do脚本,删除quit -force,防止重新启动仿真时关闭软件
    • 2、修改xxxxxxxx_simulate.do脚本,删除run 1000ns和quit -force,添加log -r ./*,并且添加do {xxxx_compile.do}
  • 总结


一、自动生成tb文件

1、对于被生成的模块的端口格式,必须按照以下格式,才能自动生成。

##    entity top is
##       Port ( 
##          clk_in  : in std_logic;
##          rst     : in std_logic;
##          data_in : in std_logic;
##          data_out: out std_logic_vector(7 downto 0);
##          data_v  : out std_logic_vector(7 downto 0);
##       );
##    end top;

2、预定义字符串列表,用于生成tb文件的基本框架,如下:

str1 = ['----------------------------------------------------------------------------------'
'-- Company: \n',
'-- Engineer: \n',
'-- \n',
'-- Create Date: \n',
'-- Design Name: \n',
'-- Module Name:  - Behavioral\n',
'-- Project Name: \n',
'-- Target Devices: \n',
'-- Tool Versions: \n',
'-- Description: \n',
'-- \n',
'-- Dependencies: \n',
'-- \n',
'-- Revision:\n',
'-- Revision 0.01 - File Created\n',
'-- Additional Comments:\n',
'-- \n',
'----------------------------------------------------------------------------------\n',
'\n',
'\n',
'library IEEE;\n',
'use IEEE.STD_LOGIC_1164.ALL;\n',
'\n',
'-- Uncomment the following library declaration if using\n',
'-- arithmetic functions with Signed or Unsigned values\n',
'--use IEEE.NUMERIC_STD.ALL;\n',
'\n',
'-- Uncomment the following library declaration if instantiating\n',
'-- any Xilinx leaf cells in this code.\n',
'--library UNISIM;\n',
'--use UNISIM.VComponents.all;\n',
'\n',
'entity top is\n',
'-- port ();\n',
'end top;\n',
'\n',
'architecture Behavioral of top is\n',
'\n',
'   constant period  : TIME                          := 20 NS;  --时钟周期设置\n',
'   constant CNT_MAX : INTEGER                       := 100  ;  --cnt计数器最大值\n',
'   signal   cnt     : INTEGER RANGE 0 TO CNT_MAX    := 0    ;  --复位设置\n',
'begin\n',
'-----------------------复位和时钟生成----------------------------------------\n',
'   clk_gen1 : PROCESS \n',
'   BEGIN\n',
'      clk_in <= \'1\';\n',
'      WAIT FOR period/2;\n',
'      clk_in <= \'0\';\n',
'      WAIT FOR period/2;\n',
'   END PROCESS;\n',
' \n',
'   cnt : process(clk)\n',
'   begin\n',
'      if ( clk_in\'event and clk_in = \'1\') then\n',
'         if (cnt = CNT_MAX) then\n',
'            cnt <= 0;\n',
'         else\n',
'            cnt <= cnt + 1;\n',
'         end if;\n',
'      end if;\n',
'   end process; \n',
'\n',
'   reset : process(clk)\n',
'   begin\n',
'      if ( clk_in\'event and clk_in = \'1\') then\n',
'         if cnt > CNT_MAX then\n',
'            reset <= \'0\';  \n',
'         else\n',
'            reset <= \'1\';  --复位\n',
'         end if;\n',
'      end if;\n',
'   end process;\n',
'\n',
'----------------------------UUT instantiate--------------------------\n',
'   Port map(\n',
'   );\n',
'\n',
'end Behavioral;']

3、读取需要仿真的模块顶层文件,读取到环境列表中

vhdfile = open('\xxx.vhd','r')                #读取文件
vhdfilelines = vhdfile.readlines()           #将文件列表写入vhdfilelines中
vhdfile.close()     


4、从vhdfilelines中循环查找端口、模块名称

entitystr = 'entity'                            #实体-识别字符串
endstr = 'end '                                 #实体结束部分-识别字符串
numend = 0                                      #端口开始的行号
numstart = 0                                    #端口结束的行号

for listline in vhdfilelines:                   #循环读取文件列表
   if entitystr in listline:                    
      strline = listline                        
      numstart = vhdfilelines.index(strline)    #根据‘实体-识别字符串’查找对应的行号
      strline = strline.split(' ')              #按照空格来分割
      entityname = strline[1]                   #模块名称赋值
      endstr = endstr + entityname              #重新定义:实体结束部分-识别字符串
      continue                                  #跳出本次循环
   if endstr in listline:                       #查找结束位置行号
      strline = listline                        
      numend = vhdfilelines.index(strline)      
      break                                     #结束循环

5、提取要仿真的模块端口信息

portlist = vhdfilelines[numstart+2:numend-1]                       #提取模块实体部分
row_n = len(portlist)                                              #确定长度
column_n = 2                                                       #列表的列数
portmap = [['' for columns in range(2)] for rows in range(row_n)]  #定义一个n行2列的空列表

for i in range(0,row_n):                                           #for循环查找对应信息
   strline = portlist[i].split(':')                                
   strline1 = strline[0].split(' ')                                
   for j in range(0,len(strline1)):                                
      if strline1[j] != '':                                        
         portmap[i][0] = strline1[j]                               #端口名称赋值
         break                                                     
   portmap[i][1] = strline[1]

6、修改实体、结构体部分的模块名称,如下:

ModuleName = "tb_" + entityname
str1[5] = "-- Module Name: " + ModuleName + " - Behavioral\n"
str1[32] = "entity " + ModuleName + " is\n"
str1[34] = "end " + ModuleName + ";\n"
str1[36] = "architecture Behavioral of " + ModuleName + " is\n"

7、生成tb文件中调试模块的信号定义

for i in range(row_n-1,-1,-1):                     #逆序循环插入
   strline = portmap[i][0]                         #信号名称
   strline = " "*tabnum + "signal " + strline      #生成:"   signal clk_in"
   if portnamenum > len(strline):                  #判断是否超出最大数
      kongnum = portnamenum - len(strline)         
   else:                                           
      kongnum = 1                                  
   strline = strline + " "*kongnum + ":"           #补入空格,对其到":"
   if "in " in portmap[i][1]:                      #去掉第二段字符串的in、out方向关键字
      strline1 = portmap[i][1].replace("in ","")   
   elif "out" in portmap[i][1]:                    
      strline1 = portmap[i][1].replace("out","")   
   strline = strline + strline1                    #拼接
   str1.insert(38,strline)                         #插入

8、生成测试实体说明部分

str1.insert(38," "*tabnum + "end component;\n")              
str1.insert(38,"   );\n")                                    
for i in range(row_n-1,-1,-1):                               #倒序插入
   strline = portmap[i][0]                                   #
   strline = " "*tabnum*2 + strline                          #
   if portnamenum > len(strline):                            #判断是否超出最大数
      kongnum = portnamenum - len(strline)         
   else:                                           
      kongnum = 1 
   strline = strline + " "*kongnum + ":"                     #
   strline = strline + portmap[i][1]                         #
   str1.insert(38,strline)                                   #
str1.insert(38,"   port(\n")                                 #
str1.insert(38," "*tabnum + "component "+ entityname +"\n")  #

9、生成测试实体的例化部分信号连接

numinst = str1.index('   Port map(\n')                  #由于上述插入多行,这里需要重新定为行号
str1.insert(numinst,"   UUT : "+ entityname +"\n")           # 

for i in range(row_n-1,-1,-1):                               #倒序插入
   strline = portmap[i][0]                                   #
   strline = " "*tabnum*2 + strline                          #
   if portnamenum > len(strline):                            #判断是否超出最大数
      kongnum = portnamenum - len(strline)         
   else:                                           
      kongnum = 1 
   strline = strline + " "*kongnum + "=> "                   #
   strline = strline + portmap[i][0] + " "*(20-len(portmap[i][0]))#
   if i != row_n-1:
      strline = strline + ',\n'
   else:
      strline = strline + '\n'
   str1.insert(numinst+2,strline)                            #

10、将修改好的激励文件写入到对应的tb文件中

fileName= source_path + '\\' + ModuleName + '.vhd'
with open(fileName,'w',encoding='utf-8')as file:
   file.writelines(str1)

二、对于vivado使用modelsim仿真时修改代码不退出重新加载的方法

1、修改修改xxxxxxxx_Compile.do脚本,删除quit -force,防止重新启动仿真时关闭软件

CompileDoFile = open('.\xxx_Compile.do', 'r')
CompileDoFileAllLines = CompileDoFile.readlines()
CompileDoFile.close()
CompileDoFile = open('.\xxx_Compile.do', 'w')
for EachLine in CompileDoFileAllLines:
    if EachLine.find('quit -force') == -1:
        CompileDoFile.writelines(EachLine)
CompileDoFile.close()

2、修改xxxxxxxx_simulate.do脚本,删除run 1000ns和quit -force,添加log -r ./*,并且添加do {xxxx_compile.do}

SimulateDoFile = open('.\xxx_ simulate.do', 'r')
SimulateDoFileAllLines = SimulateDoFile.readlines()
SimulateDoFile.close()

dowrite = SimulateDoFileAllLines[8]
if "vsim" in dowrite:
   SimulateDoFileAllLines.insert(8,"do {xxxx_compile.do}\n")

SimulateDoFile = open('.\xxx_ simulate.do', 'w')
for EachLine in SimulateDoFileAllLines:
    #EachLine.find('run 1000ns')表示列表的某一元素中存在该find字符串,如果不存在,则返回-1
    if EachLine.find('run 1000ns') == -1 and EachLine.find('quit -force') == -1:
        SimulateDoFile.writelines(EachLine)
if ('log -r ./*\n' not in SimulateDoFileAllLines) == True:
   SimulateDoFile.writelines('\nlog -r ./*\n')
SimulateDoFile.close()

总结

对于生成的tb文件使用了基本的python语言,对文件读写操作的语法,我以前也没有怎么学过Python,但是经过2天的学习,就掌握了基本语法,可以看懂程序。并进行编写上述代码。第一个程序解决了我们每次编写仿真激励时的麻烦,在自动生成的tb文件中,会自动生成时钟和复位的代码,也会将测试的模块例化到tb文件中,开发人员只需完成模块的其它输入信号的仿真编写。在自动重启时加载修改后的代码,并不会关闭modelsim界面,这样也节省了我们开发人员的仿真时间。可以更加高效的办公。

你可能感兴趣的:(FPGA,fpga开发,python)