diff --git a/nmigen/FPGA_pins.xlsx b/nmigen/FPGA_pins.xlsx new file mode 100644 index 0000000..2ecf161 Binary files /dev/null and b/nmigen/FPGA_pins.xlsx differ diff --git a/nmigen/fpga_config.py b/nmigen/fpga_config.py new file mode 100644 index 0000000..3d9bf38 --- /dev/null +++ b/nmigen/fpga_config.py @@ -0,0 +1,24 @@ + +# If the design does not create a "sync" clock domain, it is created by the nMigen build system +# using the platform default clock (and default reset, if any). + +from nmigen import * +from humpback import * + + +class SimpleBlink(Elaboratable): + def elaborate(self, platform): + led = platform.request("user_led", 0) + counter = Signal(24) + pin = platform.request("gpioa", 0) + + m = Module() + m.d.sync += counter.eq(counter + 1) + m.d.comb += led.o.eq(counter[23]) + m.d.comb += pin.o.eq(led) + return m + + +if __name__ == "__main__": + platform = HumpbackPlatform() + platform.build(SimpleBlink(), do_program=False) diff --git a/nmigen/humpback.py b/nmigen/humpback.py new file mode 100644 index 0000000..70a5c18 --- /dev/null +++ b/nmigen/humpback.py @@ -0,0 +1,112 @@ +# Strongly inspired by the migen build of humpback +# Using STM32 Nucleo-H743ZI2 board +# Note to self: Pin assignment differs from Nucleo-H743ZI + +import os +import subprocess + +from nmigen.build import * +from nmigen.vendor.lattice_ice40 import * +from nmigen_boards.resources import * +from resources import * +from pin_mapper import * + + +__all__ = ["HumpbackPlatform"] + + +class HumpbackPlatform(LatticeICE40Platform): + device = "iCE40HX8K" # Using ICE40HX8K-CT256 + package = "CT256" + default_clk = "clk25" # Point of deviation: Clock speed for humpback is fixed at 25MHz + + # Acquire GPIO mappings from gpio_mapper + gpio_dict, global_gpio_dict = GPIOMapping() + resources = [ + + # Define clock + Resource("clk25", 0, Pins("K9", dir="i"), + Clock(25e6), Attrs(GLOBAL=True, IO_STANDARD="SB_LVCMOS") + ), + + # Define user LED + Resource("user_led", 0, Pins("H3", dir="o"), + Attrs(IO_STANDARD="SB_LVCMOS") + ), + + # Serial interfaces: Only including pins usable to STM32 + + # Define UART interfaces + # UART interface: There are no pull ups from humpback. + # Need to configure STM32 pins to pull up. + # Note: Use USART in asynchronous mode + UARTResource(0, + rx="T11", tx="M13", rts="M15", cts="T10", + attrs=Attrs(IO_STANDARD="SB_LVCMOS", PULLUP=1) + ), + + # UART1 interface: Read note for UART interface above + # UART1 interface is broken due to pin rearrangement introduced for Nucleo-H743ZI2 + # Uncomment if fixed, or found an alternative (e.g. bit banging UART) +# *UARTResource(1, +# tx="M11", rx="T13", rts="A6", cts="B16", +# attrs=Attrs(IO_STANDARD="SB_LVCMOS", PULLUP=1) +# ), + + # Define SPI interfaces + # Note: Use "role=device" to make humpback a SPI slave + # The ~CS pin is a global pin, but not being configured global. + SPIResource(0, + cs="R2", clk="C8", mosi="N5", miso="T2", + attrs=Attrs(IO_STANDARD="SB_LVCMOS") + ), + + # Define I2C interface + # Note: Need to program pull up in stm32 + # Use "role=device" to make humpback a I2C slave + I2CResource(0, + sda="T16", scl="M12", + attrs=Attrs(IO_STANDARD="SB_LVCMOS", PULLUP=1) + ), + + + # TODO:STM32 GPIO pins, ignore other unusable pins as well + *GPIOResources( + pins_dict = gpio_dict, dir = "o", + attrs=Attrs(IO_STANDARD="SB_LVCMOS"), + ), + *GPIOResources( + pins_dict = global_gpio_dict, dir = "o", + attrs=Attrs(GLOBAL=True, IO_STANDARD="SB_LVCMOS"), + ), + + ] + + connectors = [] + + # tool chain setup, using default ICE40 HX8K evaluation code + def toolchain_program(self, products, name): + iceprog = os.environ.get("ICEPROG", "iceprog") + with products.extract("{}.bin".format(name)) as bitstream_filename: + # TODO: this should be factored out and made customizable + subprocess.check_call([iceprog, "-S", bitstream_filename]) + +if __name__ == "__main__": + from nmigen_boards.test.blinky import * + HumpbackPlatform().build(Blinky(), do_program=False) + + + + + + + + + + + + + + + + diff --git a/nmigen/pin_mapper.py b/nmigen/pin_mapper.py new file mode 100644 index 0000000..c740522 --- /dev/null +++ b/nmigen/pin_mapper.py @@ -0,0 +1,105 @@ +import pandas as pd + + +# Get dictionaries of: +# - Non-global GPIO to FPGA mapping +# - Global GPIO to FPGA mapping +# (Ordered) +def GPIOMapping(): + + # Standard extraction of data from excel file + data = pd.read_excel("nmigen/FPGA_pins.xlsx", skiprows = range(1,2)) + df = pd.DataFrame(data, columns=["Designator", "Pin Name", "Net"]) + + stm32 = df[df.Net.str.startswith("STM32")] + + # Change some GPIO mapping for Nucleo-H743ZI2 + # Created a mapping from old pins to new pins + old_to_new_dict = { + # Old : New + "PC1" :"PB1" , + "PC4" :"PC2" , + "PC5" :"PF10" , + "PA1" :"PB2" , + "PA7" :"PE9" , + "PA8" :"PF2" , + "PA9" :"PF1" , + "PA10" :"PF0" , + "PB1" :"PF4" , + "PC2" :"PF5" , + "PA2" :"PF6" , + "PB6" :"PG6" , + "PE13" :"PG12" , + "PF14" :"PE14" , + "PE14" :"PE6" , + } + + # Extract data from stm32 dataframe to a dictionary + gpio_dict = {} + global_gpio_dict = {} + + for index, row in stm32.iterrows(): + + # Replace old pins with new pins + # Note: There are 2 PE6 pins on Nucleo-H743ZI2 + # This will remove 1 mapping of PE6, keys cannot be duplicated + key = row["Net"].split("_")[1] + if key in old_to_new_dict: + key = old_to_new_dict.pop(key) + + dict_entry = {key: row["Designator"]} + + # Insert mappings into dictionary + if "BIN" in row["Pin Name"]: + global_gpio_dict.update(dict_entry) + else: + gpio_dict.update(dict_entry) + + + return gpio_dict, global_gpio_dict + + +# Function to provide mapping EEM pins to differential pins +# Usage: +# Positive pin: [][][0] +# Negative pin: [][][1] +def diffMapping(): + + eem0 = [ + # P+ve , N-ve + [ "J3" , "H1" ], + [ "F5" , "B1" ], + [ "C1" , "C2" ], + [ "F4" , "D2" ], + [ "G5" , "D1" ], + [ "G4" , "E3" ], + [ "H5" , "E2" ], + [ "G3" , "F3" ], + ] + + eem1 = [ + # P+ve , N-ve + [ "L6" , "L3" ], + [ "H6" , "F1" ], + [ "H4" , "G2" ], + [ "J4" , "H2" ], + [ "J2" , "J1" ], + [ "K1" , "K3" ], + [ "L4" , "L1" ], + [ "K4" , "M1" ], + ] + + eem2 = [ + # P+ve , N-ve + [ "J5" , "G1" ], + [ "K5" , "M2" ], + [ "L7" , "N2" ], + [ "M6" , "M3" ], + [ "L5" , "N3" ], + [ "P1" , "M4" ], + [ "P2" , "M5" ], + [ "R1" , "N4" ], + ] + + return [eem0, eem1, eem2] + diff --git a/nmigen/resources.py b/nmigen/resources.py new file mode 100644 index 0000000..0eeb3bc --- /dev/null +++ b/nmigen/resources.py @@ -0,0 +1,90 @@ +from nmigen.build import * + + +__all__ = ["I2CResource", "GPIOResources", "DiffResources"] + + +def I2CResource(*args, sda, scl, conn=None, attrs=None, role="host"): + assert role in ("host", "device") + + io = [] + + # sda line: I/O port for the data line + io.append(Subsignal("sda", Pins(sda, dir="io", conn=conn, assert_width=1))) + + # sck line: I2C clock signal outputs from master to slave + if role == "host": + io.append(Subsignal("scl", Pins(scl, dir="o", conn=conn, assert_width=1))) + else: #device + io.append(Subsignal("scl", Pins(scl, dir="i", conn=conn, assert_width=1))) + + if attrs is not None: + io.append(attrs) + return Resource.family(*args, default_name="i2c", ios=io) + +# Auto create a resource list given a set of iCE40 pins and STM32 pin names (pins_dict) +def GPIOResources(*args, pins_dict, dir = "o", invert=False, conn=None, attrs=None): + + # Check data integrity: pins_dict must be a dict AND port must be from a to k + assert isinstance(pins_dict, dict) + + # Debug: dir == "o" + assert dir == "o" + + # List of resources to be returned + resources = [] + for STM32_pin, iCE40_pin in pins_dict.items(): + + # Set all gpio pins to be output only for the time being + + # TODO: Allow dir argument. + ios = [Pins(iCE40_pin, dir=dir, invert=invert, conn=conn)] + if attrs is not None: + ios.append(attrs) + + # Extract GPIO port and port number from STM32_pin + # Strip "P" from P + if STM32_pin.startswith('P'): + STM32_pin = STM32_pin[1:] + + # Acquire port from + port = STM32_pin[0].lower() + port_num = int(STM32_pin[1:]) + + # Insert gpio. into resources list + resources.append(Resource.family(*args, port_num, default_name=("gpio"+port), ios=ios)) + return resources + +# Auto create a resource list for differential I/O +def DiffResources(*args, eem_pins, invert=False, conn=None, attrs=None, dir): + # TODO: Everything + + # assert dimensionality + assert isinstance(eem_pins, list) + assert isinstance(eem_pins[0], list) + assert isinstance(eem_pins[0][0], list) + assert isinstance(eem_pins[0][0][0], str) + + # assert direction to be either input or output + # reject tristate or bidirectional pin + assert dir in ("i", "o") + + + + +if __name__ == "__main__": + + from pin_mapper import * + eem = diffMapping() + DiffResources(eem_pins = eem, dir = "o", + attrs=Attrs(IO_STANDARD="SB_LVCMOS") + ) + + + + + + + + +