m-labs / migen

A Python toolbox for building complex digital hardware
https://m-labs.hk/migen
Other
1.22k stars 210 forks source link

Better FMC support #269

Open kaolpr opened 1 year ago

kaolpr commented 1 year ago

For FMC TDC and Shuttler debugging, I've created some helpers for dealing with FMC that I'd like to submit to your critical review ;-).

Observations

The problem

It seems to be beneficial to provide some decoupling between FMC module and platform specific code as it is done with e.g. differential inputs or synchronizers.

LA and HA banks can have different IO standards than HB (both single ended and differential). Those standards can be both platform-specific and FMC specific. Not all carriers support VADJ adjustment (e.g. FMC2 @ AFCK), and some do not accept VIO_B_M2C (e.g. EEM FMC Carrier). FMC also not always follow specification (e.g. Shuttler and VIO_B_M2C).

So for FMC modules using all 3 FMC banks and mixing single-ended and differential signals, a number of different IO standards can be in use. Hard-coding them in FMC module will impact decoupling from particular platform.

Passing them to the io function may be a solution, but it seems a bit cumbersome. The developer would then need to match signals with appropriate voltage levels. This can be easily performed inside platform class.

Suggested solution

For applications, I've come across, mostly two types of signals are used (excluding MGT and clocking that uses CLKx lines): single-ended IOs with appropriate voltage standard and LVDS. For that reason, I assumed that those two should be supported in the first place.

As bank voltages can not be really deducted due to different implementations, I assumed they have to be given on per gateware design basis. In my opinion, instantiation of the platform is a good place to pass desired voltage configuration.

The platform would then be able to provide appropriate IO standard for the given bank.

For example: defining a single-ended signal on bank LA using a platform initialized with VADJ=1.8V would lead to, in the case of Xilinx platform, LVCMOS18.

On the other hand, attempt to define differential signal (by default in LVDS standard) on VADJ=3.3V configured Kintex 7 platform would rise an exception, as such configuration is not supported.

Designer intent to use a specific type of IO standard (e.g. single-ended or differential) would be expressed by using an "IO standard keyword". Such keyword would be used when instantiating IOStandard class. During execution of add_extension method, the platform would replace "keyword IO standards" with those applicable to the given platform configuration.

In the example below, two keywords are used: single and diff, expressing will of using single-ended and differential LVDS types, respectively. More keywords can be implemented, if needed.

If one wants to force a specific IO standard (thereby breaking FMC-platform decoupling), instead of a keyword a desired IO standard prefixed with force:<IO standard> can be used. In the example, this feature is illustrated with IOStandard("force:FANCY_HARDCODED_STD").

Implementation example:

from migen import *
from migen.build.generic_platform import *
from migen.build.fmc import _fmc_pin
from migen.build.platforms.digilent_genesys2 import Platform as Genesys2

class DemoFMC:
    @classmethod
    def io(cls, fmc):
        return [
            (f"fmc{fmc}_single_ended", 0,
                Pins(_fmc_pin(fmc, "HB", 0, "p")),
                IOStandard("single")
            ),
            (f"fmc{fmc}_differential", 0,
                Subsignal("p", Pins(_fmc_pin(fmc, "LA", 0, "p"))),
                Subsignal("n", Pins(_fmc_pin(fmc, "LA", 0, "n"))),
                IOStandard("diff")
            ),
            (f"fmc{fmc}_fancy", 0,
                Pins(_fmc_pin(fmc, "HB", 2, "p")),
                IOStandard("force:FANCY_HARDCODED_STD")
            ),
        ]

    @classmethod
    def add_std(cls, target, fmc):
        target.platform.add_extension(cls.io(fmc))

        target.platform.request(f"fmc{fmc}_single_ended")
        target.platform.request(f"fmc{fmc}_differential")
        target.platform.request(f"fmc{fmc}_fancy")

class DemoTarget(Module):
    def __init__(self, platform):
        self.platform = platform

platform = Genesys2(fmc1_vadj=2.5, fmc1_vio_b_m2c=1.8)
target = DemoTarget(platform)
DemoFMC.add_std(target, 1)

platform.build(target, run=False, build_dir="./build")

Genesys2 has its IO standards defined in the following way:

iostd = {
  1.2: {"diff": "LVDS", "single": "LVCMOS12"},
  1.8: {"diff": "LVDS_25", "single": "LVCMOS18"},
  2.5: {"diff": "LVDS_25", "single": "LVCMOS25"},
  3.3: {"diff": None, "single": "LVCMOS33"},
}

Therefore, example code generates the following constraints:

 ## fmc1_single_ended:0
set_property LOC G13 [get_ports {fmc1_single_ended}]
set_property IOSTANDARD LVCMOS18 [get_ports {fmc1_single_ended}]
 ## fmc1_differential:0.p
set_property LOC D27 [get_ports {fmc1_differential_p}]
set_property IOSTANDARD LVDS_25 [get_ports {fmc1_differential_p}]
 ## fmc1_differential:0.n
set_property LOC C27 [get_ports {fmc1_differential_n}]
set_property IOSTANDARD LVDS_25 [get_ports {fmc1_differential_n}]
 ## fmc1_fancy:0
set_property LOC L15 [get_ports {fmc1_fancy}]
set_property IOSTANDARD FANCY_HARDCODED_STD [get_ports {fmc1_fancy}]

(...)
sbourdeauducq commented 1 year ago

This could be generalized beyond FMC by simply defining platform-independent I/O standards, e.g. some Enum with common signaling standards that could be used in place of IOStandard. It seems that is the main point of this proposal. I would not bother with VADJ/VIO_B_M2C and leave it to the user to sort out at the Migen application level, it seems everybody has their own sauce there anyway.

On the other hand, attempt to define differential signal (by default in LVDS standard) on VADJ=3.3V configured Kintex 7 platform would rise an exception, as such configuration is not supported.

The rules there are complicated and best left to the vendor tools.