Skip to content

Instantly share code, notes, and snippets.

@adamgreig
Last active August 22, 2020 19:12
Show Gist options
  • Save adamgreig/4d111d3c52da035c6ca897ea2356ce59 to your computer and use it in GitHub Desktop.
Save adamgreig/4d111d3c52da035c6ca897ea2356ce59 to your computer and use it in GitHub Desktop.
import subprocess
import nmigen as nm
from nmigen.build import Resource, Pins, PinsN, Attrs, Clock, Subsignal
from nmigen.vendor.lattice_ecp5 import LatticeECP5Platform
class UART(nm.Elaboratable):
def __init__(self, data, divider=217, n=8):
assert divider >= 1
self.valid = nm.Signal()
self.tx_o = nm.Signal()
self.data = nm.Const(data, n)
self.divider = divider
self.n = n
def elaborate(self, platform):
m = nm.Module()
tx_div = nm.Signal(range(self.divider))
tx_reg = nm.Signal(self.n+2, reset=1)
tx_cnt = nm.Signal(range(self.n+3))
m.d.comb += self.tx_o.eq(tx_reg[0])
with m.If(tx_cnt == 0):
# Idle
with m.If(self.valid):
m.d.sync += [
tx_reg.eq(nm.Cat(0, self.data, 1)),
tx_cnt.eq(self.n+2),
tx_div.eq(self.divider - 1),
]
with m.Else():
# Transmitting
with m.If(tx_div != 0):
# Wait for clock divider
m.d.sync += tx_div.eq(tx_div - 1)
with m.Else():
# Update output state
m.d.sync += [
tx_reg.eq(nm.Cat(tx_reg[1:], 1)),
tx_cnt.eq(tx_cnt - 1),
tx_div.eq(self.divider - 1),
]
return m
class ColorLite5A75E_V6_0_Platform(LatticeECP5Platform):
device = "LFE5U-25F"
package = "BG256"
speed = "6"
default_clk = "clk25"
lvcmos = Attrs(IO_TYPE="LVCMOS33")
resources = [
Resource("clk25", 0, Pins("P6", dir="i"), Clock(25e6), lvcmos),
Resource("led", 0, Pins("T6", dir="o"), lvcmos),
Resource("key", 0, PinsN("R7", dir="i"), lvcmos),
Resource(
"flash", 0,
Subsignal("cs", Pins("N8", dir="o"), lvcmos),
Subsignal("so", Pins("T7", dir="i"), lvcmos),
Subsignal("si", Pins("T8", dir="o"), lvcmos)),
Resource(
"led_common", 0,
Subsignal("a", Pins("N5", dir="o"), lvcmos),
Subsignal("b", Pins("N3", dir="o"), lvcmos),
Subsignal("c", Pins("P3", dir="o"), lvcmos),
Subsignal("d", Pins("P4", dir="o"), lvcmos),
Subsignal("e", Pins("N4", dir="o"), lvcmos),
Subsignal("clk", Pins("M3", dir="o"), lvcmos),
Subsignal("lat", Pins("N1", dir="o"), lvcmos),
Subsignal("oe", Pins("M4", dir="o"), lvcmos)),
Resource(
"eth_common", 0,
Subsignal("mdc", Pins("R5", dir="o"), lvcmos),
Subsignal("mdio", Pins("T4", dir="io"), lvcmos),
Subsignal("rst", PinsN("R6", dir="o"), lvcmos)),
Resource(
"phy", 0,
Subsignal("txc", Pins("L1", dir="o"), lvcmos),
Subsignal("txd", Pins("M2 M1 P1 R1", dir="o"), lvcmos),
Subsignal("txctl", Pins("L2", dir="o"), lvcmos),
Subsignal("rxc", Pins("J1", dir="i"), lvcmos),
Subsignal("rxd", Pins("J3 K2 K1 K3", dir="i"), lvcmos),
Subsignal("rxctl", Pins("J2", dir="i"), lvcmos)),
Resource(
"phy", 1,
Subsignal("txc", Pins("J16", dir="o"), lvcmos),
Subsignal("txd", Pins("K16 J15 J14 K15", dir="o"), lvcmos),
Subsignal("txctl", Pins("K14", dir="o"), lvcmos),
Subsignal("rxc", Pins("M16", dir="i"), lvcmos),
Subsignal("rxd", Pins("M15 R16 L15 L16", dir="i"), lvcmos),
Subsignal("rxctl", Pins("P16", dir="i"), lvcmos)),
Resource(
"sdram", 0,
Subsignal("we", PinsN("B5", dir="o"), lvcmos),
Subsignal("cas", PinsN("A6", dir="o"), lvcmos),
Subsignal("ras", PinsN("B6", dir="o"), lvcmos),
Subsignal("ba", Pins("B7 A8", dir="o"), lvcmos),
Subsignal("a", Pins("A9 B9 B10 C10 D9 C9 E9 D8 E8 C7 B8", dir="o"),
lvcmos),
Subsignal("d",
Pins("D5 C5 E5 C6 D6 E6 D7 E7 D10 C11 D11 C12 E10 C13 "
"D13 E11 A5 B4 A4 B3 A3 C3 A2 B2 D14 B14 A14 B13 "
"A13 B12 B11 A11", dir="io"),
lvcmos),
Subsignal("clk", Pins("C8", dir="o"), lvcmos)),
]
connectors = []
# Used by __init__ to create each individual LED pin header
leds = [
# R0, G0, B0, R1, G1, B1
['C4', 'D4', 'E4', 'D3', 'E3', 'F4'], # J1
['F3', 'F5', 'G3', 'G4', 'H3', 'H4'], # J2
['G5', 'H5', 'J5', 'J4', 'B1', 'C2'], # J3
['C1', 'D1', 'E2', 'E1', 'F2', 'F1'], # J4
['G2', 'G1', 'H2', 'K5', 'K4', 'L3'], # J5
['L4', 'L5', 'P2', 'R2', 'T2', 'R3'], # J6
['T3', 'R4', 'M5', 'P5', 'N6', 'N7'], # J7
['P7', 'M7', 'P8', 'R8', 'M8', 'M9'], # J8
['P11', 'N11', 'M11', 'T13', 'R12', 'R13'], # J9
['R14', 'T14', 'D16', 'C15', 'C16', 'B16'], # J10
['B15', 'C14', 'T15', 'P15', 'R15', 'P12'], # J11
['P13', 'N12', 'N13', 'M12', 'P14', 'N14'], # J12
['H15', 'H14', 'G16', 'F16', 'G15', 'F15'], # J13
['E15', 'E16', 'L12', 'L13', 'M14', 'L14'], # J14
['J13', 'K13', 'J12', 'H13', 'H12', 'G12'], # J15
['G14', 'G13', 'F12', 'F13', 'F14', 'E14'], # J16
]
# Currently unknown inputs/outputs/bidirectional
inputs = ['A10', 'A12']
outputs = ['A7', 'A15', 'E12', 'E13', 'K12', 'M6', 'M13']
bidis = ['D12']
def __init__(self):
lvcmos = self.lvcmos
# Create resources for each LED header
for jidx, pins in enumerate(self.leds):
self.resources += [Resource(
"led_rgb", jidx,
Subsignal("r0", Pins(pins[0], dir="o"), lvcmos),
Subsignal("g0", Pins(pins[1], dir="o"), lvcmos),
Subsignal("b0", Pins(pins[2], dir="o"), lvcmos),
Subsignal("r1", Pins(pins[3], dir="o"), lvcmos),
Subsignal("g1", Pins(pins[4], dir="o"), lvcmos),
Subsignal("b1", Pins(pins[5], dir="o"), lvcmos))]
# Create resources for each unknown pin
for pin in self.outputs:
self.resources += [Resource(pin, 0, Pins(pin, dir="o"), lvcmos)]
for pin in self.inputs:
self.resources += [Resource(pin, 0, Pins(pin, dir="i"), lvcmos)]
for pin in self.bidis:
self.resources += [Resource(pin, 0, Pins(pin, dir="io"), lvcmos)]
super().__init__()
class Top(nm.Elaboratable):
def elaborate(self, platform):
m = nm.Module()
# Use OSCG so we can still clock with PHYs in reset
# (otherwise, the PHYs stop running the XO).
m.domains.sync = cd_osc = nm.ClockDomain("sync")
m.submodules.oscg = nm.Instance("OSCG", p_DIV=12, o_OSC=cd_osc.clk)
# Hold PHYs in reset
m.d.comb += platform.request("eth_common").rst.eq(1)
# Hold SDRAM in WE to prevent it driving DQ and leave clock low.
sdram = platform.request("sdram")
m.d.comb += sdram.we.eq(1), sdram.clk.eq(0)
# Flash LED
led = platform.request("led")
ctr = nm.Signal(22)
m.d.sync += ctr.eq(ctr + 1)
m.d.comb += led.o.eq(ctr[-1])
# UART on unknown outputs
v = nm.Signal()
p = nm.Signal()
m.d.sync += p.eq(ctr[-4]), v.eq(p != ctr[-4])
for idx, pin in enumerate(platform.outputs):
print(f"{idx:02X} {pin}")
uart = UART(idx)
m.submodules += uart
pin = platform.request(pin)
m.d.comb += pin.o.eq(uart.tx_o), uart.valid.eq(v)
return m
def main():
platform = ColorLite5A75E_V6_0_Platform()
platform.build(Top(), ecppack_opts=["--compress"])
subprocess.run(["ffp", "ecp5", "program", "build/top.bit"])
if __name__ == "__main__":
main()
"""
Takes a Project Trellis config file and iodb.json and attempts to work out
the pin assignment.
"""
import re
import json
import argparse
from natsort import natsorted
def load_iodb(dbpath, package):
with open(dbpath) as f:
iodb = json.load(f)
packages = iodb["packages"]
if package not in packages:
raise RuntimeError(
f"Package {package} not found, try {list(packages.keys())}")
return packages[package]
def load_config(path):
tiles = {}
loc = None
name = None
with open(path) as f:
for line in f:
if line.startswith(".tile "):
match = re.search(r"R(\d+)C(\d+):([A-Z0-9_]*)$", line)
row = int(match.group(1))
col = int(match.group(2))
name = match.group(3)
loc = (row, col)
if loc not in tiles:
tiles[loc] = {}
tiles[loc][name] = {}
elif line.startswith("enum: "):
key, value = line.split()[1:]
tiles[loc][name][key] = value.strip()
return tiles
def get_base_type(tiles, row, col, name, pio):
if (row, col) in tiles and name in tiles[(row, col)]:
tile = tiles[(row, col)][name]
return tile.get(f'{pio}.BASE_TYPE')
def reduce_base_types(types):
bidi = any(t.startswith("BIDIR_") for t in types if t)
inp = any(t.startswith("INPUT_") for t in types if t)
out = any(t.startswith("OUTPUT_") for t in types if t)
modes = sorted(list(set("_".join(t.split("_")[1:]) for t in types if t)))
if bidi or (inp and out):
return "bidi", modes
elif inp:
return "input", modes
elif out:
return "output", modes
else:
return None
def top_pin(tiles, row, col, pio):
# Top pins are defined in PIOT0, PIOT1, PICT0, PICT1.
return reduce_base_types([
get_base_type(tiles, row, col, "PIOT0", f"PIO{pio}"),
get_base_type(tiles, row, col+1, "PIOT1", f"PIO{pio}"),
get_base_type(tiles, row+1, col, "PICT0", f"PIO{pio}"),
get_base_type(tiles, row+1, col+1, "PICT1", f"PIO{pio}"),
])
def btm_pin(tiles, row, col, pio):
# Bottom pins are defined in PICB0 and PICB1.
# Sometimes EFBx_PICBy or SPICB0.
return reduce_base_types([
get_base_type(tiles, row, col, "PICB0", f"PIO{pio}"),
get_base_type(tiles, row, col, "SPICB0", f"PIO{pio}"),
get_base_type(tiles, row, col, "EFB0_PICB0", f"PIO{pio}"),
get_base_type(tiles, row, col, "EFB2_PICB0", f"PIO{pio}"),
get_base_type(tiles, row, col+1, "PICB1", f"PIO{pio}"),
get_base_type(tiles, row, col+1, "EFB1_PICB1", f"PIO{pio}"),
get_base_type(tiles, row, col+1, "EFB3_PICB1", f"PIO{pio}"),
])
def left_pin(tiles, row, col, pio):
# Left pins are defined in PICL0, PICL1, PICL2.
# Sometimes PICL2 is in MIB_CIB_LR.
# Sometimes PICLx is shared with DQSy.
return reduce_base_types([
get_base_type(tiles, row, col, "PICL0", f"PIO{pio}"),
get_base_type(tiles, row, col, "PICL0_DQS2", f"PIO{pio}"),
get_base_type(tiles, row+1, col, "PICL1", f"PIO{pio}"),
get_base_type(tiles, row+1, col, "PICL1_DQS0", f"PIO{pio}"),
get_base_type(tiles, row+1, col, "PICL1_DQS3", f"PIO{pio}"),
get_base_type(tiles, row+2, col, "PICL2", f"PIO{pio}"),
get_base_type(tiles, row+2, col, "PICL2_DQS1", f"PIO{pio}"),
get_base_type(tiles, row+2, col, "MIB_CIB_LR", f"PIO{pio}"),
])
def right_pin(tiles, row, col, pio):
# Right pins are defined in PICR0, PICR1, PICR2
return reduce_base_types([
get_base_type(tiles, row, col, "PICR0", f"PIO{pio}"),
get_base_type(tiles, row, col, "PICR0_DQS2", f"PIO{pio}"),
get_base_type(tiles, row+1, col, "PICR1", f"PIO{pio}"),
get_base_type(tiles, row+1, col, "PICR1_DQS0", f"PIO{pio}"),
get_base_type(tiles, row+1, col, "PICR1_DQS3", f"PIO{pio}"),
get_base_type(tiles, row+2, col, "PICR2", f"PIO{pio}"),
get_base_type(tiles, row+2, col, "PICR2_DQS1", f"PIO{pio}"),
get_base_type(tiles, row+2, col, "MIB_CIB_LR", f"PIO{pio}"),
get_base_type(tiles, row+2, col, "MIB_CIB_LR_A", f"PIO{pio}"),
])
def main():
parser = argparse.ArgumentParser()
parser.add_argument("config", help="Path to .config file to read")
parser.add_argument("--package", default="CABGA256",
help="Footprint to use, default CABGA256")
parser.add_argument(
"--dbpath",
default="/usr/share/trellis/database/ECP5/LFE5U-25F/iodb.json",
help="Path to database file, default is LFE5U-25F")
args = parser.parse_args()
iodb = load_iodb(args.dbpath, args.package)
tiles = load_config(args.config)
max_row = max(p['row'] for p in iodb.values())
max_col = max(p['col'] for p in iodb.values())
pins = {}
inputs = []
outputs = []
bidis = []
for pin in natsorted(iodb):
pinrow = iodb[pin]['row']
pincol = iodb[pin]['col']
pinpio = iodb[pin]['pio']
io = None
if pinrow == 0:
io = top_pin(tiles, pinrow, pincol, pinpio)
elif pinrow == max_row:
io = btm_pin(tiles, pinrow, pincol, pinpio)
elif pincol == 0:
io = left_pin(tiles, pinrow, pincol, pinpio)
elif pincol == max_col:
io = right_pin(tiles, pinrow, pincol, pinpio)
else:
raise RuntimeError(f"Unhandled pin location {pin}")
if io:
pins[pin] = io
print(f"{pin} {io[0]} {','.join(io[1])}")
if io[0] == "input":
inputs.append(pin)
elif io[0] == "output":
outputs.append(pin)
elif io[0] == "bidi":
bidis.append(pin)
else:
print(f"{pin} unused")
print("Inputs:", inputs)
print("Outputs:", outputs)
print("Bidis:", bidis)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment