本节主要说明的如何使用LiteX描述自己的开发板。
一、Platform文件和Target文件
对于一个传统的FPGA工程,我们除了需要提供编写的RTL工程代码,还需要提供该代码的运行硬件环境,即板卡的信息,包括FPGA芯片信息,外设引脚约束等;因此对于一个完整的FPGA工程来说,编译生成Bit文件需要两部分的信息:
-
与工程具体逻辑代码无关的平台相关信息:包括芯片型号、引脚约束等;在LiteX中,这部分信息位于Platform文件夹中,每个板卡都有一个对应的文件进行描述;
-
与工程具体逻辑代码相关的信息:即具体的逻辑电路;在LiteX中,这部分信息位于Target文件夹中,如果基于某一个Platform有多个工程,则可以创建多个Target文件;
下图展示了LiteX目前官方已经支持的部分板卡型号的Platform文件和Target文件:
图1 LiteX的Platform文件
图2 LiteX的Target文件
以Xilinx VC707板卡为例,platforms文件夹中的xilinx_vc707.py文件的部分内容如下:
图3 VC707的Platform文件示意图1
图4 VC707的Platform文件示意图2
可以发现,该文件中主要定义了开发板上与FPGA芯片固定相连的外设的引脚约束信息,通常包括时钟、灯、按钮、DDR、以太网、串口等。格式为由一系列的元组(在Python中由圆括号定义)组成的列表(由方括号定义)。例如_io是一个列表,而
("clk200", 0,
Subsignal("p", Pins("E19"), IOStandard("LVDS")),
Subsignal("n", Pins("E18"), IOStandard("LVDS")),
)
则是包含四个元素的元组。
在py文件中定义了上述信息后,则可以为该开发板创建对应的Platform:
图5 VC707的Platform文件示意图3
后续将进一步介绍该步骤。
二、基于Migen添加新的开发板定义
可以发现,如果要对Xilinx FPGA开发板创建新的Platform,则需要继承来自LiteX的Xilinx7SeriesPlatform类或其他Xilinx平台类。这些基础类都是LiteX自己定义的,与Migen中的platforms已经基本无关。但是方法仍然相似,因此,我们首先介绍基于Migen的开发板的platform文件定义。
Migen将开发板的定义都放在build/platforms目录下,如下图所示:
图6 Migen的Platform文件
下面的代码描述了为Lattice公司的FPGA创建一个Migen platform的简单过程(不是LiteX的Platform,再次注意!):
from migen.build.generic_platform import *
from migen.build.lattice import LatticePlatform
from migen.build.lattice.programmer import IceStormProgrammer
class Platform(LatticePlatform):
default_clk_name = "clk12"
default_clk_period = 83.333
def __init__(self):
LatticePlatform.__init__(self, "ice40-1k-tq144", _io, _connectors,
toolchain="icestorm")
def create_programmer(self):
return IceStormProgrammer()
Migen还有AlteraPlatform、XilinxPlatform等平台,而这些FPGA厂商相关的Platform文件又继承自更基础的GenericPlatform类,GenericPlatform的构造函数(即__init__)接收以下参数:
-
device:即FPGA芯片的型号,是一个字符串,与FPGA厂商有关,例如XC7Z100-1FFG900I或者上面的点中的ice40-1k-tq144;
-
io:一个由特定格式的元组组成的列表,该列表代表了当前开发板上所有非用户可扩展的I/O资源,例如LED、SPI等;
-
connectors:也是一个由特定格式的元组组成的列表;表示用户可扩展的I/O,默认情况下不连接到任何硬件;
-
name:作用未知;
-
toolchain:该FPGA芯片所需要使用EDA工具,同一个厂商可能提供了多个工具,例如Xilinx提供了ISE和Vivado;通过该参数来选择调用哪个工具链;
基础类GenericPlatform中定义了default_clk_name和default_clk_period两个类变量且没有赋值,因此继承自GenericPlatform的类需要对这两个类变量进行赋值。
赋值给default_clk_name的值需要与定义在io列表中的表示时钟输入的IO资源名称一致,例如,图3中的FPGA时钟输入IO资源名称为“clk200”。
default_clk_period的单位为纳秒,设置之后将会为default_clk_name所代表的时钟创建对应的约束。
default_clk_name所代表的时钟域的默认名称即为使用LiteX快速创建FPGA SoC工程(2)中提到的“sys”。
最后:
def create_programmer(self):
return IceStormProgrammer()
该函数应该返回的是FPGA厂商相关的编程器(烧写器),本文不作介绍。
三、Finalization
使用LiteX快速创建FPGA SoC工程(2)中LiteX所使用的Finalization机制正是来源于Migen。
在整个设计的所有参数都确定之前,某些电路逻辑的行为或者大小可能会被发生变化,例如某个状态机的状态数量可能与外部外设的数量有关,导致状态寄存器的位宽是不确定的。
在这种情况下,Migen提供了Finalization机制,以Migen的lx9_microbard.py为例,如下图所示:
LX9 Microboard开发板具有以太网外设,并且以太网部分的时钟约束与设计中的其他部分有所区别,因此在do_finalize方法中使用基类GenericPlatform提供的lookup_request(“eth_clocks”)方法检测当前设计是否使用了以太网外设,并在必要时,向要综合的当前设计添加适当的新的约束;如果设计中没有使用以太网外设,则不会添加额外的约束,并忽略lookup_request所返回的ConstraintError。
Finalization将会对一个名为Fragment的Migen内部的数据结构进行操作。需要非常了解Migen的内部结构才能正确使用Fragment,因此如果需要有条件的添加约束,可以参考上述示例。当然也可以分别使用基类GenericPlatform的add_period_constaint和add_platform_command方法在设计中的任何节点手动添加约束。
在图6下方的代码短片中,没有定义do_finalize方法,这是因为对于该FPGA平台来说,不存在任何需要特殊约束的外设,所以省略了该方法的定义。
四、io和connectors
在完成对Platform的定义之后,需要定义_io和_connectors,然后将它们传递给Platform的构造器函数。
4.1 io的定义
这里的io指的是非用户可扩展的引脚,即绑定了固定外设的引脚。一个io元组的格式如下:
io_name, id, Pins("pin_name", "pin_name")/Subsignal(...), IOStandard("std_name"), Misc("misc"))
- io_name是外设的名称,在使用request函数请求获取某个io时,将会传递该名称。
-
id是一个数字,用于区分功能相同的外设的多个副本,例如LED灯;
-
对于简单的外设,Pins是一个helper类(即为了便捷的使用某个资源而创建的辅助类型的类),应该包含与外设连接的FPGA引脚的标识符字符串;将在后续讨论Subsignal;
这些元组用于在Migen生成的Verilog代码中创建输入和输出端口的名称,并在Migen生成的约束文件中为FPGA引脚约束提供正确的名称。
Subsignals也是用于使用FPGA引脚的helper类,而且这些引脚可以根据使用目的进一步进行分类。Subsignals的输入与io元组相同,只是省略了id。使用Subsignals的结果是,这些进一步分类的引脚资源会被封装为一个类,该类的Signal可以通过类成员来访问。例如以下代码,串口serial要使用2个FPGA引脚,并且这两个FPGA引脚能清晰的根据使用目的进行分类,因此使用进行区分:
# Serial
("serial", 0,
Subsignal("tx", Pins("V12")),
Subsignal("rx", Pins("U12")),
IOStandard("LVCMOS33")
),
# usage
comb += [serial.tx.eq(some_signal)]
这在Migen中称为Record,Record还提供了许多有用的方法来快速构建Migen的声明语句,可以将Record视为C语言中的structs关键字。I/O外设是否使用完全取决于自己的判断。
io元组的其余输入是可选的,例如IOStandard,它也是一个helper类,用于表示引脚应该使用哪个电压标准。Misc也是一个helper类,通常包含一个以空格分割的其他信息字符串,例如slew rate、是否启用上拉电阻等,这些信息应该与一起放入约束文件中。
具有相同功能但仅引脚不同的外设资源应该在各自的元组内通过递增id来说明:
("user_led", 0, Pins("99"), IOStandard("LVCMOS33")),
("user_led", 1, Pins("98"), IOStandard("LVCMOS33")),
("user_led", 2, Pins("97"), IOStandard("LVCMOS33")),
("user_led", 3, Pins("96"), IOStandard("LVCMOS33")),
("user_led", 4, Pins("95"), IOStandard("LVCMOS33")),
此外,在io中,至少需要存在一个与的值相匹配的时钟信号,Migen将会自动request该名称匹配的时钟。
4.2 connectors
再次说明,connectors代表了用户可扩展的I/O引脚;connector的定义非常简单:
_connectors = [
("pmoda", "F4 F3 E2 D2 H2 G2 C2 C1"),
("pmodb", "C4 B2 B3 B4 B1 A1 A3 A4"),
("pmodc", "C5 C6 B6 C7 A5 A6 B7 D8"),
]
conn_name类似于io_name,connector元组的第二个参数是与FPGA板卡相关的、以空格分隔的引脚名称。例如以下代码创建了三个connector元组,引脚名称的顺序应该按照对连接器有意义的某种顺序列出:
_connectors = [
("pmoda", "F4 F3 E2 D2 H2 G2 C2 C1"),
("pmodb", "C4 B2 B3 B4 B1 A1 A3 A4"),
("pmodc", "C5 C6 B6 C7 A5 A6 B7 D8"),
]
默认情况下,与connector的引脚相关联的外设不能直接使用Platform通过request方法获取,而是先要使用add_extension添加,如下所示:
my_i2c_device = [
("i2c_device", 0,
Subsignal("sdc", Pins("pmoda:2"), Misc("PULLUP")),
Subsignal("sda", Pins("pmoda:3"), Misc("PULLUP"))
)
]
plat.add_extension(my_i2c_device)
plat.request("i2c_device")
注意,使用connector的引脚的外设,Pins()的输入不再是引脚名称,而是“conn_name:index”,其中index是从0开始的索引。上述代码的i2c使用了pmoda连接器的E2和D2引脚。
此外还可以一次性设置所有的Subsignal的约束,如下代码,为所有的Subsignal都设置了相同的电平标准LVCMOS33:
("irda", 0,
Subsignal("rx", Pins("106")),
Subsignal("tx", Pins("105")),
Subsignal("sd", Pins("107")),
IOStandard("LVCMOS33")
),
五、基于LiteX添加新的开发板定义
LiteX的开发板定义与Migen几乎一致。区别在于,虽然Platform所继承的基类(例如XIlinxPlatform)的名称与Migen一致,但是实际上已经不是Migen所提供的基类,而是LiteX的基类,LiteX的基类的功能基本上包含了Migen的基类的功能。这里不过多的赘述。
没有回复内容