天天看點

Python_Project---PYCPLDIntroductionAuto_Generate.pyips.generatormap_iolook_up_iplook_upparser_pin_in_outlook_up_table

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)== :
        boardyml = raw_input("Please input yaml file path\n>>")
    else:
        boardyml = sys.argv[]

    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: 

PMH:
    CMD: PMH
    SW2:
        T_PIN: CPLD_IO4
        A_PIN: B58
        DIRECTION: A2T
        NOTE: SW2- fly wire to J8-


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檔案:

:
    TYPE: NONE
    FUNC: L4
    PINS: CPLD_IO1
    #used by L4 with J7 -
:
    TYPE: NONE
    FUNC: NONE
    PINS: CPLD_IO2
:
    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
:
    TYPE: T
    FUNC: NONE
    PINS: B7
           

上面是twrv2_cpld_io.yml中的一段。

TYPE有三種定義:

  1. NONE 代表這個IO口連到闆子的jumper上,沒有連到ELEV闆子上,也不作特定的連接配接輔助闆K70的IO。
  2. A代表代表這個IO口連到闆子的jumper上,作特定連接配接輔助闆K70的IO。
  3. 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  = 
  switch = False

  try:
    SingF = modules["SINGLE"]
    del modules["SINGLE"]
  except KeyError:
    pass

  if SingF == : 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
           

parser_pin_in_out

look_up_table