Introduction
Github 上有一个开源的项目PYCPLD. 它的功能是用python脚本语言来集成CPLD的工程。
This project is to use the python script to integrate different CPLD IP into target board system. Currently the Altera MAX-II EP570P is supported with Quartus-15.1 version. and the Arduio compatible CPLD board is added in reference design.
你只需要按照它的模板来添加你的IP模块,其他的集成IP模块进系统和生成对应的Quartus工程就可以通过执行python脚本自动生成。
本文主要就是对其代码进行解析,阐述其实现原理。
你可以到github上搜索源码进行学习。有任何疑问,可以在评论中留言,我会尽快解答。
Auto_Generate.py
判断输入命令,如果为python ./Auto_Generate.py twrkv58f220m_SDK_2_0_enc.yml 格式 且 指定的yml文件存在,就执行g.generate(boardyml)来生成文件。
g = ips.generator,所以我们重点看ips.generator.
'''import语句作用就是用来导入模块的,它可以出现在程序中的任何位置。
在用import语句导入模块时最好按照这样的顺序:
1、python 标准库模块
2、python 第三方模块
3、自定义模块'''
import sys, os
import ips.generator
__PATH__ = os.path.dirname(os.path.abspath(__file__)).replace('\\','/')
if __name__ == '__main__':
print (" Verilog Code Generator Tool")
print (" -----------------------------------\n")
g = ips.generator
if len(sys.argv)== 1:
boardyml = raw_input("Please input yaml file path\n>>")
else:
boardyml = sys.argv[1]
if os.path.exists(boardyml) is True:
g.generate(boardyml)
else:
print ("error path: %s"%boardyml)
sys.stdout.flush()
os.system("pause")
ips.generator
ips.generator就是ips文件夹下的generator模块。
def generate(boardyml):
## context data
context = {
# 'MODULE_TEXT': 'module top(clk,rst_n,rs232_rx,rs232_tx, %s, led);',
'MODULE_TEXT': 'module top(clk,rst_n,rs232_rx, %s, led);',
'INOUT_TEXT': '',
'WIRE_TEXT': '',
'REG_TEXT': '',
'ASSIGN_TEXT': '',
'IP_TEXT': '',
'INIT_REG_TEXT': '',
'CMD_CASE_TEXT': '',
'RST_REG_TEXT': '',
'DFT_REG_TEXT': ''
}
ret = analysis_yml(boardyml, context)
if ret is None:
return None
## render template with context data
print "rend data*******************"
topv = engine.render(VERILOG_TEMPLATE_PATH, context)
save_template_to_file(VERILOG_OUTPUT_PATH, 'top', topv)
print VERILOG_OUTPUT_PATH
ret = analysis_yml(boardyml, context)
分析yml
#analyze the boardyml and update the context for tempale
def analysis_yml(boardyml, context):
io_dic, bus_scope = analysis.analysis_context(boardyml)
print "io_dic"
print io_dic
print "bus_scope"
print bus_scope
if io_dic is None:
return None
IO_DIC = io_dic
INIT_REG_TEXT, CMD_CASE_TEXT, RST_REG_TEXT, DFT_REG_TEXT = cpld.Code_verilog_reg(io_dic)
context['INIT_REG_TEXT'] = as_escaped(INIT_REG_TEXT)
context['CMD_CASE_TEXT'] = as_escaped(CMD_CASE_TEXT)
context['RST_REG_TEXT'] = as_escaped(RST_REG_TEXT)
context['DFT_REG_TEXT'] = as_escaped(DFT_REG_TEXT)
MODULE_TEXT = cpld.Module_header(bus_scope)
context['MODULE_TEXT'] = as_escaped(MODULE_TEXT)
INOUT_TEXT = cpld.inout(bus_scope)
context['INOUT_TEXT'] = as_escaped(INOUT_TEXT)
WIRE_TEXT = cpld.wire(io_dic)
context['WIRE_TEXT'] = as_escaped(WIRE_TEXT)
REG_TEXT = cpld.reg(io_dic)
context['REG_TEXT'] = as_escaped(REG_TEXT)
ASSIGN_TEXT = cpld.assign(io_dic, bus_scope)
context['ASSIGN_TEXT'] = ASSIGN_TEXT
IP_TEXT = cpld.ip_caller(io_dic)
context['IP_TEXT'] = IP_TEXT
return True
io_dic, bus_scope = analysis.analysis_context(boardyml)
调用analysis模块中的analysis_context函数。返回的是一个IO口的词典和bus_scope神马东东的。
##################################################################
#AUTO-GENERATE-API
##################################################################
def analysis_context(boardyml):
global CPLD_IO_TABLE
global BOARD_CPLD_IO_PATH
global CPLD_QSF_TEMPL_PATH
global CPLD_TCL_TEMPL_PATH
file_name = os.path.basename(boardyml)
dir_name = os.path.dirname(boardyml)
##########################################################################
#Choose cpld. According cpld versio and type, load cpld io and template path
##########################################################################
#TYPE: TWR CPLD
if re.search("twr", file_name):
#TWR CPLD V2
if re.search("v2", dir_name):
cpld_io_path = BOARD_CPLD_IO_PATH['TWR_CPLD_V2']
CPLD_QSF_TEMPL_PATH = BOARD_CPLD_QSF_TEMPLATE['TWR_CPLD_V2']
CPLD_TCL_TEMPL_PATH = BOARD_CPLD_TCL_TEMPLATE['TWR_CPLD_V2']
#TWR CPLD V1
else:
cpld_io_path = BOARD_CPLD_IO_PATH['TWR_CPLD_V1']
CPLD_QSF_TEMPL_PATH = BOARD_CPLD_QSF_TEMPLATE['TWR_CPLD_V1']
CPLD_TCL_TEMPL_PATH = BOARD_CPLD_TCL_TEMPLATE['TWR_CPLD_V1']
#TYPE: FRDM CPLD
elif re.search("frdm", file_name):
#FRDM CPLD V2
if re.search("v2", dir_name):
cpld_io_path = BOARD_CPLD_IO_PATH['FRDM_CPLD_V2']
CPLD_QSF_TEMPL_PATH = BOARD_CPLD_QSF_TEMPLATE['FRDM_CPLD_V2']
CPLD_TCL_TEMPL_PATH = BOARD_CPLD_TCL_TEMPLATE['FRDM_CPLD_V2']
#FRDM CPLD V1
else:
cpld_io_path = BOARD_CPLD_IO_PATH['FRDM_CPLD_V1']
CPLD_QSF_TEMPL_PATH = BOARD_CPLD_QSF_TEMPLATE['FRDM_CPLD_V1']
CPLD_TCL_TEMPL_PATH = BOARD_CPLD_TCL_TEMPLATE['FRDM_CPLD_V1']
#TYPE: CPLD EP570
elif re.search("ep570", dir_name):
cpld_io_path = BOARD_CPLD_IO_PATH['EP570_CPLD']
CPLD_QSF_TEMPL_PATH = BOARD_CPLD_QSF_TEMPLATE['EP570_CPLD']
CPLD_TCL_TEMPL_PATH = BOARD_CPLD_TCL_TEMPLATE['EP570_CPLD']
else:
print "Error: Unknown CPLD Version!"
return
#load yml files
modules = file_load_yml( boardyml )
CPLD_IO_TABLE = file_load_yml( cpld_io_path )
if modules is None or CPLD_IO_TABLE is None:
print ("Error: Load file error.")
return None
#map cpld pins with boards(target & assitant), return a dic
io_dic = map_io(modules)
print io_dic
if io_dic is None:
return None, None
bus_scope, Used_io = cpld_io_analyze(io_dic)
if Used_io is None:
print ("Error: Bus Definition!")
return None, None
#Generate my_uart_top.v
#---------------------------------------------
#---------------------------------------------
return io_dic, bus_scope
#generate_top_v_file(internal_io_dic, external_io_dic, bus_scope)
加载cpld_io和qsf、 tcl文件的文件路径
通过os.path.basename和os.path.dirname得到boardyml的完整路径和文件名。
通过re.search(“twr”, file_name)和re.search(“v2”, dir_name)正则匹配,选择对应的CPLD板。根据CPLD板的版本来加载对应cpld_io文件路径和quartus工程所需的qsf tcl文件的文件路径
tcl文件:tcl的全称是Tool command
language,是基于字符串的命令语言,tcl语言是一种解释性语言,他不需要通过编译与联结,它像其它shell语言一样,直接对每条语句顺次解释执行。在FPGA的应用中tcl文件中使用tcl语言对管脚进行配置,tcl文件只包含管脚的配置信息。 qsf文件:qsf的全称是Quartus Settings
File的缩写。包含了一个Quartus工程的所有约束,包括工程信息、器件信息、引脚约束、编译约束和用于Classic
TimingAnalyzer的时序约束。
(Quartus Settings File 都在pycpld/ips/template目录下)。
modules = file_load_yml( boardyml )
打开boardyml文件,用yaml.load进行加载。
yaml.load的作用是把一个yaml格式的文档转换成python对象。
##################################################################
#FILE OPERATIONS SECTION
##################################################################
def file_load_yml(fp):
try:
f = open(fp,"r")
except IOError:
print ("Error: No such file %s"%fp)
return None
c = {}
try:
c = yaml.load(f)
except yaml.parser.ParserError:
print ("Error: There may be some format errors exist in Yaml File.\n%s"%fp)
return None
f.close()
return c
下面是节选的一个twrkv58f220m_SDK_2_0_test.yml文件:
#Descriptions:
#CMD: cpld command
#A: Assitant Board
#T: Target Board
#FUNC0: the function of this pin connection
#DIRECTION: A2T T2A T2T
#SINGLE: default 0, if the pin header is single on FRDM-Board, this should be set 1
SINGLE: 0
PMH:
CMD: PMH
SW2:
T_PIN: CPLD_IO4
A_PIN: B58
DIRECTION: A2T
NOTE: SW2-1 fly wire to J8-1
ENC:
IP: __ENC
CMD: ENC
pha_out:
PIN: A34
DIRECTION: out
phb_out:
PIN: A33
DIRECTION: out
index_out:
PIN: CPLD_IO50
DIRECTION: out
主要有两种情况:
第一种就是简单的连线
第二种就是ip模块的输出输入配置(ENC正交编码模块)
生成的dict的结构如下:
<cmd>: [(ioin0,inout0,comments),...(ioin,ioout,comments), <module_name>]
Struct io_dic:
{
<moudle_cmd_0>: [(ioin0,inout0,comments),...(ioin,ioout,comments), module_name],
<moudle_cmd_1>: [(ioin0,inout0,comments),...(ioin,ioout,comments), module_name],
<moudle_cmd_2>: [(ioin0,inout0,comments),...(ioin,ioout,comments), module_name],
...
}
CPLD_IO_TABLE = file_load_yml( cpld_io_path )
读取cpld_io这个IO管脚信息的yml文件,同样,返回的是关于CPLD_IO信息的一个词典。
下面是节选的一个cpld_io.xxx.yml文件:
1:
TYPE: NONE
FUNC: L4
PINS: CPLD_IO1
#used by L4 with J7 2-3.
2:
TYPE: NONE
FUNC: NONE
PINS: CPLD_IO2
21:
TYPE: A
FUNC: SPI1_CLK_K70
PINS: B7_K70
# 22: //这些是cpld的TDI TDO TCK TMS这些下载或者电源 GND的IO
# TYPE: NONE
# FUNC: NONE
# PINS: X
# 23:
# TYPE: NONE
# FUNC: NONE
# PINS: X
# 24:
# TYPE: NONE
# FUNC: NONE
# PINS: X
# 25:
# TYPE: NONE
# FUNC: NONE
# PINS: X
26:
TYPE: T
FUNC: NONE
PINS: B7
上面是twrv2_cpld_io.yml中的一段。
TYPE有三种定义:
- NONE 代表这个IO口连到板子的jumper上,没有连到ELEV板子上,也不作特定的连接辅助板K70的IO。
- A代表代表这个IO口连到板子的jumper上,作特定连接辅助板K70的IO。
- T是Target Board的意思,这些cpld的io口都连到了ELEV板子上
注意CPLD_IO_TABLE是一个全局变量
io_dic = map_io(modules)
通过前面的modules(这个由用户定义的IO连线的yml文件生成得到的dict)匹配(定义好的CPLD板子的IO端口说明生成得到的dict),得到匹配好的IO端口连线的dict:io_dic
因为CPLD_IO_TABLE是一个全局变量,所以这里map_io只带了modules这个参数。
map_io—>look_up—>parser_pin_in_out—>look_up_table—>global CPLD_IO_TABLE
下面是重点,目录升级到最高.
map_io
def map_io(modules):
'''
<cmd>: [(ioin0,inout0,comments),...(ioin,ioout,comments), <module_name>]
Struct io_dic:
{
<moudle_cmd_0>: [(ioin0,inout0,comments),...(ioin,ioout,comments), module_name],
<moudle_cmd_1>: [(ioin0,inout0,comments),...(ioin,ioout,comments), module_name],
<moudle_cmd_2>: [(ioin0,inout0,comments),...(ioin,ioout,comments), module_name],
...
}
'''
SingF = 0
switch = False
try:
SingF = modules["SINGLE"]
del modules["SINGLE"]
except KeyError:
pass
if SingF == 1: switch = True
io_dic = {}
#look up io in cpld for each module
for module_name in modules:
module = modules[module_name]
CMD = module["CMD"]
if ( "IP" in module):
io_dic[CMD] = look_up_ip(module, switch)
if io_dic[CMD] is None:
print ("Error: Pin error, Please check your yml file at module: \'%s\'."%module_name)
return None
io_dic[CMD].append(module_name)
else:
io_dic[CMD] = look_up(module, switch)
if io_dic[CMD] is None:
print ("Error: Pin error, Please check your yml file at module: \'%s\'."%module_name)
return None
io_dic[CMD].append(module_name)
build_in_module = {'IP': '__UART', 'CMD': 'BIM'}
io_dic['UART'] = look_up_ip(build_in_module, switch)
return io_dic
入口参数modules是如下的词典
{'ENC': {'phb_out': {'DIRECTION': 'out', 'PIN': 'A33'}, 'IP': '__ENC', 'CMD': 'ENC', 'pha_out': {'DIRECTION': 'out', 'PIN': 'A34'}, 'index_out': {'DIRECTION': 'out', 'PIN': 'CPLD_IO50'}}, 'PMH': {'CMD': 'PMH', 'SW2': {'NOTE': 'SW2-1 fly wire to J8-1', 'DIRECTION': 'A2T', 'A_PIN': 'B58', 'T_PIN': 'CPLD_IO4'}}}
for module_name in modules:
module = modules[module_name]
CMD = module["CMD"]
dict = {"b":"2", "a":"1",
"c":"3", "e":"5", "d":"4"}
for key,value in dict.items():
print(key,value)
# -- OUTPUT --
# a 1
# c 3
# b 2
# e 5
# d 4
'''
输出的顺序却不是我们预想的那样初始化的顺序,查询相关文献得知,Python保证遍历字典所有元素,但不保证遍历的顺序,
'''
module就是
{'phb_out': {'DIRECTION': 'out', 'PIN': 'A33'}, 'IP': '__ENC', 'CMD': 'ENC', 'pha_out': {'DIRECTION': 'out', 'PIN': 'A34'}, 'index_out': {'DIRECTION': 'out', 'PIN': 'CPLD_IO50'}}
CMD得到的就是‘ENC’
如果是内建IP模块,调用look_up_ip(module, switch)
如果是连线,调用look_up(module, switch)
上面两步生成的io_dic中添加UART这个默认模块(每一个工程都需要一个UART的IP用来接收CMD)
build_in_module = {'IP': '__UART', 'CMD': 'BIM'}
io_dic['UART'] = look_up_ip(build_in_module, switch)
look_up_ip
{‘phb_out’: {‘DIRECTION’: ‘out’, ‘PIN’: ‘A33’}, ‘IP’: ‘__ENC’, ‘CMD’: ‘ENC’, ‘pha_out’: {‘DIRECTION’: ‘out’, ‘PIN’: ‘A34’}, ‘index_out’: {‘DIRECTION’: ‘out’, ‘PIN’: ‘CPLD_IO50’}}
#look up io in cpld_io dic for internal IPs
#return: [(CpldIOIn0,CpldIoOut0,Comments),....] type[list]
#switch: single row need to remap, this value set True
def look_up_ip(module, switch):
global IP_DICT
mname = module["CMD"]
print "module name: " + mname
#Python 字典(Dictionary) keys() 函数以列表list返回一个字典dict所有的键key。
confuncs = module.keys()
#remove() 函数用于移除列表中某个值的第一个匹配项。
confuncs.remove("CMD")
#search the IP package and find the matching IPs
'''
pkgutil.iter_modules(path=None, prefix='')
Yields (module_finder, name, ispkg) for all submodules on path, or, if path is None, all top-level modules on sys.path.
path should be either None or a list of paths to look for modules in.
prefix is a string to output on the front of every module name on output.
'''
pkgpath = os.path.dirname(ip.__file__)
cips = [name for _, name, _ in pkgutil.iter_modules([pkgpath])]
'''
['base_ip', 'enc', 'i2c_master', 'i2c_slave', 'i2c_slave_for_case', 'pwm_capture', 'pwm_out', 'qdec', 'spi_master', 'spi_slave', 'spi_slave_new', 'sw_pulse', 'uart', 'uart7bit', 'uart8bit']
'''
ip_name = None
for cip in cips:
print "checking " + cip
#print_classes()
#func = getattr(ip, cip + '.get_ip_name')
sub_module = getattr(ip, cip)
#<module 'ips.ip.enc' from 'C:\mcu_cpld_prd\mcu_cpld_prd\Auto_Generator\pycpld\ips\ip\enc\__init__.pyc'>
func = getattr(sub_module, 'get_ip_name')
#<function get_ip_name at 0x02B437F0>
sub_module_class = getattr(sub_module, func())
#ips.ip.enc.enc_partial.ENC
sub_module_class_instance = sub_module_class("")
#<ips.ip.enc.enc_partial.ENC instance at 0x02BA0850>
if sub_module_class_instance.matched_id(module['IP']) is True:
print "ID matched for " + module['IP']
ip_name = sub_module_class_instance.__class__.__name__
if sub_module_class_instance.__class__.__name__ not in IP_DICT['class']:
IP_DICT['inst'].append(sub_module_class_instance)
IP_DICT['class'].append(sub_module_class_instance.__class__.__name__)
if "ALT" in module:
print "*****************************"
print "set inst alt %s"%(module["ALT"])
sub_module_class_instance.set_alt(module["ALT"])
sub_module_class_instance.ALT_CMD = mname
elif "ALT" in module:
print "*****************************"
print "set inst alt %s"%(module["ALT"])
IP_DICT['inst'].append(sub_module_class_instance)
IP_DICT['class'].append(sub_module_class_instance.__class__.__name__)
sub_module_class_instance.set_alt(module["ALT"])
sub_module_class_instance.ALT_CMD = mname
break
if ip_name is None:
print "No matching IP found for " + module['IP']
return None
confuncs.remove("IP")
if "ALT" in confuncs:
confuncs.remove("ALT")
confuncs.sort()
look_result = []
if mname == "BIM":
look_result.append(('','',"//build in %s"%module['IP'], 'BIM', ip_name))
return look_result
for conname in confuncs:
connection = module[conname]
CPinIN, CPinOUT, comments, ip_ping_wire = parser_pin_ip(connection, conname)
if CPinIN is not None or CPinOUT is not None:
look_result.append((CPinIN,CPinOUT,comments, ip_ping_wire, ip_name))
else:
return None
return look_result
look_up
#look up io in cpld_io dic
#return: [(CpldIOIn0,CpldIoOut0,Comments),....] type[list]
#switch: single row need to remap, this value set True
def look_up(module, switch):
mname = module["CMD"]
confuncs = module.keys()
confuncs.remove("CMD")
confuncs.sort()
look_result = []
for conname in confuncs:
connection = module[conname]
CPinIN, CPinOUT, comments = parser_pin_in_out(connection, switch)
if CPinIN is not None and CPinOUT is not None:
look_result.append((CPinIN,CPinOUT,comments))
else:
return None
return look_result