本节介绍Platform类在LiteX中的定义,继承关系如下:
为某个自定义的板卡创建一个自定义的Platform时,其继承关系如下(以Xilinx7系列FPGA为例):
图1 自定义的Platform的继承关系
下面将从下至上的对这些类进行介绍。
一、GenericPlatform
GenericPlatform的所有代码如下:
# Generic Platform ---------------------------------------------------------------------------------
class GenericPlatform:
device_family = None
_jtag_support = True # JTAGBone can't be used with all FPGAs.
_bitstream_ext = None # None by default, overridden by vendor platform, may
# be a string when same extension is used for sram and
# flash. A dict must be provided otherwise
def __init__(self, device, io, connectors=[], name=None):
def request(self, *args, **kwargs):
def request_all(self, *args, **kwargs):
def request_remaining(self, *args, **kwargs):
def lookup_request(self, *args, **kwargs):
def add_period_constraint(self, clk, period, keep=True, name=None):
def add_false_path_constraint(self, from_, to):
def add_false_path_constraints(self, *clk):
def add_platform_command(self, *args, **kwargs):
def add_extension(self, *args, **kwargs):
def add_connector(self, *args, **kwargs):
def finalize(self, fragment, *args, **kwargs):
def do_finalize(self, fragment, *args, **kwargs):
def add_source(self, filename, language=None, library=None, copy=False):
def add_sources(self, path, *filenames, language=None, library=None, copy=False):
def add_source_dir(self, path, recursive=True, language=None, library=None):
def add_verilog_include_path(self, path):
def resolve_signals(self, vns):
def get_verilog(self, fragment, **kwargs):
def get_edif(self, fragment, cell_library, vendor, device, **kwargs):
def build(self, fragment):
def get_bitstream_extension(self, mode="sram"):
def create_programmer(self):
@property
def jtag_support(self):
@property
def support_mixed_language(self):
@classmethod
def fill_args(cls, toolchain, parser):
@classmethod
def get_argdict(cls, toolchain, args):
@classmethod
def toolchains(cls, device):
1.1 类变量
类变量指的是直接定义在类内部的变量(不是定义在方法中的变量)。GenericPlatform有三个类变量,如下所示:
device_family = None
_jtag_support = True # JTAGBone can't be used with all FPGAs.
_bitstream_ext = None # None by default, overridden by vendor platform, may
# be a string when same extension is used for sram and
# flash. A dict must be provided otherwise
其中device_family表示了FPGA芯片的系列,该字段将在Xilinx***Platform派生类中确定,对于Xilinx来说,可能的值为:
spartan6、7series、ultrascale、ultrascale+
_jtag_support是一个列表,定义了哪些芯片支持JTAG。在GenericPlatform的直接子类,即XilinxPlatform,中确定。
_bitstream_ext是一个列表,定义了烧写sram或者烧写flash时,所烧写的文件名后缀。在GenericPlatform的直接子类,即XilinxPlatform,中确定。
1.2 __init__构造器函数
def __init__(self, device, io, connectors=[], name=None):
self.toolchain = None
self.device = device
self.constraint_manager = ConstraintManager(io, connectors)
if name is None:
# Get name from Platform file.
name = self.__module__.split(".")[-1]
if name == "__main__":
# If no Platform file, use script filename,
name = os.path.splitext(os.path.basename(sys.argv[0]))[0]
self.name = name
self.sources = []
self.verilog_include_paths = []
self.output_dir = None
self.finalized = False
self.use_default_clk = False
# Set Platform/Device to LiteXContext.
LiteXContext.platform = self
LiteXContext.device = device
构造器函数的输入参数包括:
-
device:即FPGA芯片的名称;
-
io:非用户可扩展的IO引脚及其所连接的外设;
-
connectors:用户可扩展的IO引脚;
-
name:一般不指定,将自动从文件名或者模块名中获取;
此外,构造器函数还为Platform定义了如下成员变量:
-
toolchain:默认为None;即使用哪个EDA工具,例如vivado或者ISE;
-
sources:添加的外部源代码文件的列表,默认为空;
-
verilog_iinclude_paths:添加的外部头文件的列表,默认为空;
-
output_dir:默认为None;
-
finalized:该模块是否已经进行了Finalization过程,默认为False;
-
use_defualt_clk:是否使用默认的时钟域的名称;默认为False;
1.3 类方法
(1)以下三个方法用于从已定义好的Platform中获取对应的引脚信息:
def request(self, *args, **kwargs):
return self.constraint_manager.request(*args, **kwargs)
def request_all(self, *args, **kwargs):
return self.constraint_manager.request_all(*args, **kwargs)
def request_remaining(self, *args, **kwargs):
return self.constraint_manager.request_remaining(*args, **kwargs)
例如,假设有一个流水灯模块LedChaser,下列代码将从平台中获取对应的led灯的引脚信息,并与LedChaser模块的pads端口绑定:
if with_led_chaser:
led_pads = platform.request_all("user_led_n")
self.leds = LedChaser(
pads = led_pads,
sys_clk_freq = sys_clk_freq)
(2)以下方法用于在平台本身的定义中的do_finalize方法中,用于查询某个引脚外设是否被使用,如果被使用,则采取一定的措施,例如新增约束等:
def lookup_request(self, *args, **kwargs):
return self.constraint_manager.lookup_request(*args, **kwargs)
# usage
def do_finalize(self, fragment):
LatticeiCE40Platform.do_finalize(self, fragment)
self.add_period_constraint(self.lookup_request("clk12", loose=True), 1e9/12e6)
(3)以下方法用于添加时钟约束:
def add_period_constraint(self, clk, period, keep=True, name=None):
self.toolchain.add_period_constraint(self, clk, period, keep=keep, name=name)
def add_false_path_constraint(self, from_, to):
raise NotImplementedError
def add_false_path_constraints(self, *clk):
for a in clk:
for b in clk:
if a is not b:
self.add_false_path_constraint(a, b)
(4)以下方法用于添加其他的、时钟无关的约束:
def add_platform_command(self, *args, **kwargs):
return self.constraint_manager.add_platform_command(*args, **kwargs)
(5)以下方法一般用于添加通过用户可扩展的IO(即connector)连接的外设:
def add_extension(self, *args, **kwargs):
return self.constraint_manager.add_extension(*args, **kwargs)
(6)以下方法用于临时添加其他的connector到平台中:
def add_connector(self, *args, **kwargs):
self.constraint_manager.add_connector(*args, **kwargs)
# usage
self.add_connector(_dock_connectors)
(7)以下方法用于添加外部的源代码或头文件:
-
def add_source(self, filename, language=None, library=None, copy=False): filename = os.path.abspath(filename) if language is None: language = tools.language_by_filename(filename) if library is None: library = "work" for f, *_ in self.sources: if f == filename: return if copy: self.sources.append((filename, language, library, True)) else: self.sources.append((filename, language, library)) def add_sources(self, path, *filenames, language=None, library=None, copy=False): for f in filenames: self.add_source(os.path.join(path, f), language, library, copy) def add_source_dir(self, path, recursive=True, language=None, library=None): dir_files = [] if recursive: for root, dirs, files in os.walk(path): for filename in files: dir_files.append(os.path.join(root, filename)) else: for item in os.listdir(path): if os.path.isfile(os.path.join(path, item)): dir_files.append(os.path.join(path, item)) for filename in dir_files: _language = language if _language is None: _language = tools.language_by_filename(filename) if _language is not None: self.add_source(filename, _language, library) def add_verilog_include_path(self, path): self.verilog_include_paths.append(os.path.abspath(path))
(8)以下命令作用未知:
def get_edif(self, fragment, cell_library, vendor, device, **kwargs): return edif.convert( fragment, self.constraint_manager.get_io_signals(), cell_library, vendor, device, **kwargs)
def resolve_signals(self, vns): # Resolve signal names in constraints. sc = self.constraint_manager.get_sig_constraints() named_sc = [(vns.get_name(sig), pins, others, resource) for sig, pins, others, resource in sc] # Resolve signal names in platform commands. pc = self.constraint_manager.get_platform_commands() named_pc = [] for template, args in pc: name_dict = dict((k, vns.get_name(sig)) for k, sig in args.items()) named_pc.append(template.format(**name_dict)) return named_sc, named_pc
(9)以下方法用于编译生成bit文件和获取bit文件的扩展名:
def build(self, fragment):
raise NotImplementedError("GenericPlatform.build must be overloaded")
def get_bitstream_extension(self, mode="sram"):
"""
Return the bitstream's extension according to mode (sram / flash).
The default (generic) implementation check if `self._bitstream_ext`
is a dict or a string. For former case it return extension using `mode`
parameter, in latter case simply return `self._bitstream_ext`'s value.
When this behaviour is not adapted this method must be overriden by
a specific one at vendor level.
Parameters
----------
mode: str
bitstream destination (must be sram or flash)
Returns
-------
bitstream extension: str
"""
if self._bitstream_ext is None:
return None
elif type(self._bitstream_ext) == dict:
return self._bitstream_ext[mode]
else:
return self._bitstream_ext
(10)其他:
def create_programmer(self):
raise NotImplementedError
@property
def jtag_support(self):
if isinstance(self._jtag_support, bool):
return self._jtag_support
else:
for dev in self._jtag_support:
if self.device.startswith(dev):
return True
return False
@property
def support_mixed_language(self):
return self.toolchain.support_mixed_language
@classmethod
def fill_args(cls, toolchain, parser):
"""
pass parser to the specific toolchain to
fill this with toolchain args
Parameters
==========
toolchain: str
toolchain name
parser: argparse.ArgumentParser
parser to be filled
"""
pass # pass must be overloaded (if required)
@classmethod
def get_argdict(cls, toolchain, args):
"""
return a dict of args
Parameters
==========
toolchain: str
toolchain name
Return
======
a dict of key/value for each args or an empty dict
"""
return {} # Empty must be overloaded (if required)
@classmethod
def toolchains(cls, device):
"""
Returns list of toolchains compatible with device
Parameters
==========
device: str
device name (ice40, ecp5, nexus)
Return
======
A list of compatible toolchains (str) or an empty list
"""
if type(cls._supported_toolchains) == dict:
assert device is not None
return cls._supported_toolchains[device]
else:
return cls._supported_toolchains
二、XilinxPlatform
继承自GenericPlatform的XilinxPlatform,进一步规定了一些细节。
2.1 类成员
XilinxPlatform对GenericPlatform的三个类成员进行了具体的赋值,如下所示:
class XilinxPlatform(GenericPlatform):
_bitstream_ext = {
"sram" : ".bit",
"flash" : ".bin"
}
_supported_toolchains = {
"spartan6" : ["ise"],
"7series" : ["vivado", "f4pga", "yosys+nextpnr", "openxc7"],
"ultrascale" : ["vivado"],
"ultrascale+" : ["vivado"],
}
_jtag_support = [
"xc6",
"xc7a", "xc7k", "xc7v", "xc7z",
"xcau", "xcku", "xcvu", "xczu"
]
2.2 __init__构造器函数
主要是新增了对toolchain的判断和赋值,以及新增了edifs和ips两个成员变量:
def __init__(self, *args, toolchain="ise", **kwargs):
GenericPlatform.__init__(self, *args, **kwargs)
self.edifs = set()
self.ips = {}
if toolchain == "ise":
from litex.build.xilinx import ise
self.toolchain = ise.XilinxISEToolchain()
elif toolchain == "vivado":
from litex.build.xilinx import vivado
self.toolchain = vivado.XilinxVivadoToolchain()
elif toolchain == "symbiflow" or toolchain == "f4pga":
from litex.build.xilinx import f4pga
self.toolchain = f4pga.F4PGAToolchain()
elif toolchain in ["yosys+nextpnr", "openxc7"]:
from litex.build.xilinx import yosys_nextpnr
self.toolchain = yosys_nextpnr.XilinxYosysNextpnrToolchain(toolchain)
else:
raise ValueError(f"Unknown toolchain {toolchain}")
2.3 类方法
(1)添加网表文件和IP:
def add_edif(self, filename):
self.edifs.add((os.path.abspath(filename)))
def add_ip(self, filename, disable_constraints=False):
self.ips.update({os.path.abspath(filename): disable_constraints})
(2)在add_platform_command方法中进行了进一步的检查,检查通过后才可以调用GenericPlatform的add_platform_command方法;
def add_platform_command(self, command, **signals):
skip = False
from litex.build.xilinx import yosys_nextpnr
if isinstance(self.toolchain, yosys_nextpnr.XilinxYosysNextpnrToolchain):
# FIXME: Add support for INTERNAL_VREF to yosys+nextpnr flow.
if "set_property INTERNAL_VREF" in command:
print("WARNING: INTERNAL_VREF constraint removed since not yet supported by yosys-nextpnr flow.")
skip = True
if "set_property CFGBVS" in command:
print("WARNING: CFGBVS constraint removed since not yet supported by yosys-nextpnr flow.")
skip = True
if "set_property CONFIG_VOLTAGE" in command:
print("WARNING: CONFIG_VOLTAGE constraint removed since not yet supported by yosys-nextpnr flow.")
skip = True
if not skip:
GenericPlatform.add_platform_command(self, command, **signals)
(3)对ger_verilog、get_edif、build、add_false_path_constraint等方法进行了进一步细化:
def get_verilog(self, *args, special_overrides=dict(), **kwargs):
so = dict(common.xilinx_special_overrides)
if self.device[:3] == "xc6":
so.update(common.xilinx_s6_special_overrides)
if self.device[:3] == "xc7":
so.update(common.xilinx_s7_special_overrides)
if self.device[:4] == "xcku":
so.update(common.xilinx_us_special_overrides)
if self.device[:4] == "xcau":
so.update(common.xilinx_us_special_overrides)
so.update(special_overrides)
return GenericPlatform.get_verilog(self, *args,
special_overrides = so,
attr_translate = self.toolchain.attr_translate,
**kwargs
)
def get_edif(self, fragment, **kwargs):
return GenericPlatform.get_edif(self, fragment, "UNISIMS", "Xilinx", self.device, **kwargs)
def build(self, *args, **kwargs):
return self.toolchain.build(self, *args, **kwargs)
def add_false_path_constraint(self, from_, to):
if hasattr(from_, "p"):
from_ = from_.p
if hasattr(to, "p"):
to = to.p
self.toolchain.add_false_path_constraint(self, from_, to)
@classmethod
def fill_args(cls, toolchain, parser):
"""
pass parser to the specific toolchain to
fill this with toolchain args
Parameters
==========
toolchain: str
toolchain name
parser: argparse.ArgumentParser
parser to be filled
"""
if toolchain == "vivado":
vivado.vivado_build_args(parser)
@classmethod
def get_argdict(cls, toolchain, args):
"""
return a dict of args
Parameters
==========
toolchain: str
toolchain name
Return
======
a dict of key/value for each args or an empty dict
"""
if toolchain == "vivado":
return vivado.vivado_build_argdict(args)
else:
return dict()
三、Xilinx***Platform
该类仅设置了GenericPlatform的device_family类成员变量,如下:
# XilinxSpartan6Platform ---------------------------------------------------------------------------
class XilinxSpartan6Platform(XilinxPlatform):
device_family = "spartan6"
# Xilinx7SeriesPlatform ----------------------------------------------------------------------------
class Xilinx7SeriesPlatform(XilinxPlatform):
device_family = "7series"
# XilinxUSPlatform ---------------------------------------------------------------------------------
class XilinxUSPlatform(XilinxPlatform):
device_family = "ultrascale"
# XilinxUSPPlatform --------------------------------------------------------------------------------
class XilinxUSPPlatform(XilinxPlatform):
device_family = "ultrascale+"
至此,我们学习完了自定义Platform的所有父类。
没有回复内容