commit
eafa0fe83e
|
@ -0,0 +1,4 @@
|
|||
from litex.build.efinix.programmer import EfinixProgrammer
|
||||
from litex.build.efinix.dbparser import EfinixDbParser
|
||||
from litex.build.efinix.ifacewriter import InterfaceWriter
|
||||
from litex.build.efinix.ddr import EfinixDDR
|
|
@ -0,0 +1,43 @@
|
|||
#
|
||||
# This file is part of LiteX.
|
||||
#
|
||||
# Copyright (c) 2021 Franck Jullien <franck.jullien@collshade.fr>
|
||||
# Copyright (c) 2015-2018 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
from migen.fhdl.module import Module
|
||||
from migen.genlib.resetsync import AsyncResetSynchronizer
|
||||
|
||||
from litex.build.io import *
|
||||
|
||||
# Efinix AsyncResetSynchronizer ---------------------------------------------------------------------
|
||||
|
||||
class EfinixAsyncResetSynchronizerImpl(Module):
|
||||
def __init__(self, cd, async_reset):
|
||||
rst1 = Signal()
|
||||
self.specials += [
|
||||
Instance("EFX_FF",
|
||||
i_D = 0,
|
||||
i_SR = async_reset,
|
||||
i_CLK = cd.clk,
|
||||
i_CE = 1,
|
||||
o_Q = rst1),
|
||||
Instance("EFX_FF",
|
||||
i_D = rst1,
|
||||
i_SR = async_reset,
|
||||
i_CLK = cd.clk,
|
||||
i_CE = 1,
|
||||
o_Q = cd.rst)
|
||||
]
|
||||
|
||||
|
||||
class EfinixAsyncResetSynchronizer:
|
||||
@staticmethod
|
||||
def lower(dr):
|
||||
return EfinixAsyncResetSynchronizerImpl(dr.cd, dr.async_reset)
|
||||
|
||||
# Gowin Special Overrides --------------------------------------------------------------------------
|
||||
|
||||
efinix_special_overrides = {
|
||||
AsyncResetSynchronizer: EfinixAsyncResetSynchronizer
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
import os
|
||||
import csv
|
||||
import re
|
||||
|
||||
import xml.etree.ElementTree as et
|
||||
|
||||
namespaces = { 'efxpt' : 'http://www.efinixinc.com/peri_device_db',
|
||||
'xi' : 'http://www.w3.org/2001/XInclude'
|
||||
}
|
||||
|
||||
class EfinixDbParser():
|
||||
def __init__(self, efinity_path, device):
|
||||
self.efinity_db_path = efinity_path + '/pt/db/'
|
||||
self.device = device
|
||||
|
||||
def get_device_map(self, device):
|
||||
with open(self.efinity_db_path + 'devicemap.csv') as f:
|
||||
reader = csv.reader(f)
|
||||
data = list(reader)
|
||||
|
||||
for d in data:
|
||||
if d[0] == device:
|
||||
return d
|
||||
|
||||
return None
|
||||
|
||||
def get_package_file_name(self, dmap):
|
||||
tree = et.parse(self.efinity_db_path + dmap[2])
|
||||
root = tree.getroot()
|
||||
inc = root.findall('xi:include', namespaces)
|
||||
for i in inc:
|
||||
if 'package' in i.get('href'):
|
||||
return i.get('href').split('/')[1]
|
||||
|
||||
return None
|
||||
|
||||
def get_die_file_name(self, dmap):
|
||||
tree = et.parse(self.efinity_db_path + dmap[2])
|
||||
root = tree.getroot()
|
||||
inc = root.findall('xi:include', namespaces)
|
||||
for i in inc:
|
||||
if 'die' in i.get('href'):
|
||||
return i.get('href').split('/')[1]
|
||||
|
||||
return None
|
||||
|
||||
def get_pad_name_xml(self, dmap, pin):
|
||||
package_file = self.get_package_file_name(dmap)
|
||||
tree = et.parse(self.efinity_db_path + 'package/' + package_file)
|
||||
root = tree.getroot()
|
||||
|
||||
pm = root.findall('efxpt:package_map', namespaces)
|
||||
for p in pm:
|
||||
if p.get('package_pin') == pin:
|
||||
return (p.get('pad_name'))
|
||||
|
||||
return None
|
||||
|
||||
def get_instance_name_xml(self, dmap, pad):
|
||||
die = self.get_die_file_name(dmap)
|
||||
tree = et.parse(self.efinity_db_path + 'die/' + die)
|
||||
root = tree.getroot()
|
||||
|
||||
ipd = root.find('efxpt:io_pad_definition', namespaces)
|
||||
ios = ipd.findall('efxpt:io_pad_map', namespaces)
|
||||
for io in ios:
|
||||
if io.get('pad_name') == pad:
|
||||
return (io.get('instance'))
|
||||
|
||||
return None
|
||||
|
||||
def get_pll_inst_from_gpio_inst(self, dmap, inst):
|
||||
die = self.get_die_file_name(dmap)
|
||||
tree = et.parse(self.efinity_db_path + 'die/' + die)
|
||||
root = tree.getroot()
|
||||
|
||||
peri = root.findall('efxpt:periphery_instance', namespaces)
|
||||
for p in peri:
|
||||
if p.get('block') == 'pll':
|
||||
conn = p.findall('efxpt:single_conn', namespaces)
|
||||
for c in conn:
|
||||
if c.get('instance') == inst:
|
||||
refclk_no = 0
|
||||
if c.get('index') == '3':
|
||||
refclk_no = 1
|
||||
return (p.get('name'), refclk_no)
|
||||
|
||||
return None
|
||||
|
||||
def get_pll_inst_from_pin(self, pin):
|
||||
dmap = self.get_device_map(self.device)
|
||||
pad = self.get_pad_name_xml(dmap, pin)
|
||||
inst = self.get_instance_name_xml(dmap, pad)
|
||||
|
||||
return self.get_pll_inst_from_gpio_inst(dmap, inst)
|
||||
|
||||
def get_gpio_instance_from_pin(self, pin):
|
||||
dmap = self.get_device_map(self.device)
|
||||
return self.get_pad_name_xml(dmap, pin)
|
|
@ -0,0 +1,93 @@
|
|||
import os
|
||||
|
||||
from migen import *
|
||||
from migen.genlib.cdc import *
|
||||
from migen import ClockDomain
|
||||
|
||||
from litex.build.generic_platform import *
|
||||
from litex.soc.interconnect import axi
|
||||
|
||||
from litex.build import tools
|
||||
|
||||
class EfinixDDR(Module):
|
||||
def __init__(self, platform, config, cd):
|
||||
self.blocks = []
|
||||
self.platform = platform
|
||||
self.config = config
|
||||
self.nb_ports = 1
|
||||
|
||||
if config['ports'] != None:
|
||||
self.nb_ports = self.config['ports']
|
||||
|
||||
# TODO: set clock_domain ?
|
||||
self.port0 = port0 = axi.AXIInterface(data_width=256, address_width=32, id_width=8)
|
||||
|
||||
if self.nb_ports == 2:
|
||||
self.port1 = port1 = axi.AXIInterface(data_width=256, address_width=32, id_width=8)
|
||||
|
||||
for i in range (0, self.nb_ports):
|
||||
ios = [('axi', i,
|
||||
Subsignal('wdata', Pins(256)),
|
||||
Subsignal('wready', Pins(1)),
|
||||
Subsignal('wid', Pins(8)),
|
||||
Subsignal('bready', Pins(1)),
|
||||
Subsignal('rdata', Pins(256)),
|
||||
Subsignal('aid', Pins(8)),
|
||||
Subsignal('bvalid', Pins(1)),
|
||||
Subsignal('rlast', Pins(1)),
|
||||
Subsignal('bid', Pins(8)),
|
||||
Subsignal('asize', Pins(3)),
|
||||
Subsignal('atype', Pins(1)),
|
||||
Subsignal('aburst', Pins(2)),
|
||||
Subsignal('wvalid', Pins(1)),
|
||||
Subsignal('aaddr', Pins(32)),
|
||||
Subsignal('rid', Pins(8)),
|
||||
Subsignal('avalid', Pins(1)),
|
||||
Subsignal('rvalid', Pins(1)),
|
||||
Subsignal('alock', Pins(2)),
|
||||
Subsignal('rready', Pins(1)),
|
||||
Subsignal('rresp', Pins(2)),
|
||||
Subsignal('wstrb', Pins(32)),
|
||||
Subsignal('aready', Pins(1)),
|
||||
Subsignal('alen', Pins(8)),
|
||||
Subsignal('wlast', Pins(1)),
|
||||
)]
|
||||
|
||||
io = platform.add_iface_ios(ios)
|
||||
|
||||
port = port0
|
||||
if i == 1:
|
||||
port = port1
|
||||
|
||||
is_read = port.ar.valid
|
||||
self.comb += [io.aaddr.eq(Mux(is_read, port.ar.addr, port.aw.addr)),
|
||||
io.aid.eq(Mux(is_read, port.ar.id, port.aw.id)),
|
||||
io.alen.eq(Mux(is_read, port.ar.len, port.aw.len)),
|
||||
io.asize.eq(Mux(is_read, port.ar.size[0:4], port.aw.size[0:4])), #TODO: check
|
||||
io.aburst.eq(Mux(is_read, port.ar.burst, port.aw.burst)),
|
||||
io.alock.eq(Mux(is_read, port.ar.lock, port.aw.lock)),
|
||||
io.avalid.eq(Mux(is_read, port.ar.valid, port.aw.valid)),
|
||||
|
||||
io.atype.eq(~is_read),
|
||||
port.aw.ready.eq(io.aready),
|
||||
port.ar.ready.eq(io.aready),
|
||||
|
||||
io.wid.eq(port.w.id),
|
||||
io.wstrb.eq(port.w.strb),
|
||||
io.wdata.eq(port.w.data),
|
||||
io.wlast.eq(port.w.last),
|
||||
io.wvalid.eq(port.w.valid),
|
||||
port.w.ready.eq(io.wready),
|
||||
|
||||
port.r.id.eq(io.rid),
|
||||
port.r.data.eq(io.rdata),
|
||||
port.r.last.eq(io.rlast),
|
||||
port.r.resp.eq(io.rresp),
|
||||
port.r.valid.eq(io.rvalid),
|
||||
io.rready.eq(port.r.ready),
|
||||
|
||||
port.b.id.eq(io.bid),
|
||||
port.b.valid.eq(io.bvalid),
|
||||
io.bready.eq(port.b.ready),
|
||||
# port.b.resp ??
|
||||
]
|
|
@ -0,0 +1,371 @@
|
|||
#
|
||||
# This file is part of LiteX.
|
||||
#
|
||||
# Copyright (c) 2021 Franck Jullien <franck.jullien@collshade.fr>
|
||||
# Copyright (c) 2015-2018 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import pathlib
|
||||
import math
|
||||
import sys
|
||||
import site
|
||||
import subprocess
|
||||
import inspect
|
||||
import datetime
|
||||
|
||||
from xml.dom import expatbuilder
|
||||
import xml.etree.ElementTree as et
|
||||
|
||||
from litex.build.generic_platform import *
|
||||
|
||||
from migen.fhdl.structure import _Fragment
|
||||
from migen.fhdl.tools import *
|
||||
from migen.fhdl.namer import build_namespace
|
||||
|
||||
from litex.build.generic_platform import Pins, IOStandard, Misc
|
||||
from litex.build import tools
|
||||
|
||||
from litex.build.efinix import InterfaceWriter
|
||||
|
||||
_reserved_keywords = {
|
||||
"always", "and", "assign", "automatic", "begin", "buf", "bufif0", "bufif1",
|
||||
"case", "casex", "casez", "cell", "cmos", "config", "deassign", "default",
|
||||
"defparam", "design", "disable", "edge", "else", "end", "endcase",
|
||||
"endconfig", "endfunction", "endgenerate", "endmodule", "endprimitive",
|
||||
"endspecify", "endtable", "endtask", "event", "for", "force", "forever",
|
||||
"fork", "function", "generate", "genvar", "highz0", "highz1", "if",
|
||||
"ifnone", "incdir", "include", "initial", "inout", "input",
|
||||
"instance", "integer", "join", "large", "liblist", "library", "localparam",
|
||||
"macromodule", "medium", "module", "nand", "negedge", "nmos", "nor",
|
||||
"noshowcancelled", "not", "notif0", "notif1", "or", "output", "parameter",
|
||||
"pmos", "posedge", "primitive", "pull0", "pull1" "pulldown",
|
||||
"pullup", "pulsestyle_onevent", "pulsestyle_ondetect", "remos", "real",
|
||||
"realtime", "reg", "release", "repeat", "rnmos", "rpmos", "rtran",
|
||||
"rtranif0", "rtranif1", "scalared", "showcancelled", "signed", "small",
|
||||
"specify", "specparam", "strong0", "strong1", "supply0", "supply1",
|
||||
"table", "task", "time", "tran", "tranif0", "tranif1", "tri", "tri0",
|
||||
"tri1", "triand", "trior", "trireg", "unsigned", "use", "vectored", "wait",
|
||||
"wand", "weak0", "weak1", "while", "wire", "wor","xnor", "xor", "do"
|
||||
}
|
||||
|
||||
def get_pin_direction(fragment, platform, pinname):
|
||||
ios = platform.constraint_manager.get_io_signals()
|
||||
sigs = list_signals(fragment) | list_special_ios(fragment, True, True, True)
|
||||
special_outs = list_special_ios(fragment, False, True, True)
|
||||
inouts = list_special_ios(fragment, False, False, True)
|
||||
targets = list_targets(fragment) | special_outs
|
||||
|
||||
ns = build_namespace(list_signals(fragment) \
|
||||
| list_special_ios(fragment, True, True, True) \
|
||||
| ios, _reserved_keywords)
|
||||
ns.clock_domains = fragment.clock_domains
|
||||
|
||||
dir = "Unknown"
|
||||
|
||||
for sig in sorted(ios, key=lambda x: x.duid):
|
||||
# Better idea ???
|
||||
if (pinname.split('[')[0] == ns.get_name(sig)):
|
||||
if sig in inouts:
|
||||
dir = "inout"
|
||||
elif sig in targets:
|
||||
dir = "output"
|
||||
else:
|
||||
dir = "input"
|
||||
|
||||
return dir
|
||||
|
||||
# Timing Constraints (.sdc) ------------------------------------------------------------------------
|
||||
|
||||
def _build_sdc(clocks, false_paths, vns, named_sc, build_name, additional_sdc_commands):
|
||||
sdc = []
|
||||
|
||||
# Clock constraints
|
||||
for clk, period in sorted(clocks.items(), key=lambda x: x[0].duid):
|
||||
is_port = False
|
||||
for sig, pins, others, resname in named_sc:
|
||||
if sig == vns.get_name(clk):
|
||||
is_port = True
|
||||
if is_port:
|
||||
tpl = "create_clock -name {clk} -period {period} [get_ports {{{clk}}}]"
|
||||
sdc.append(tpl.format(clk=vns.get_name(clk), period=str(period)))
|
||||
else:
|
||||
tpl = "create_clock -name {clk} -period {period} [get_nets {{{clk}}}]"
|
||||
sdc.append(tpl.format(clk=vns.get_name(clk), period=str(period)))
|
||||
|
||||
# False path constraints
|
||||
for from_, to in sorted(false_paths, key=lambda x: (x[0].duid, x[1].duid)):
|
||||
tpl = "set_false_path -from [get_clocks {{{from_}}}] -to [get_clocks {{{to}}}]"
|
||||
sdc.append(tpl.format(from_=vns.get_name(from_), to=vns.get_name(to)))
|
||||
|
||||
# Add additional commands
|
||||
sdc += additional_sdc_commands
|
||||
|
||||
# Generate .sdc
|
||||
tools.write_to_file("{}.sdc".format(build_name), "\n".join(sdc))
|
||||
|
||||
# Peripheral configuration ------------------------------------------------------------------------
|
||||
|
||||
def _create_gpio_instance(fragment, platform, sig, pins):
|
||||
l = ""
|
||||
if len(pins) > 1:
|
||||
l = ',{},0'.format(len(pins) - 1)
|
||||
d = get_pin_direction(fragment, platform, sig)
|
||||
return 'design.create_{d}_gpio("{name}"{len})'.format(d=d, name=sig, len=l)
|
||||
|
||||
def _format_constraint(c, signame, fmt_r, fragment, platform):
|
||||
# IO location constraints
|
||||
if isinstance(c, Pins):
|
||||
tpl = 'design.assign_pkg_pin("{signame}","{pin}")\n'
|
||||
return tpl.format(signame=signame, name=fmt_r, pin=c.identifiers[0])
|
||||
|
||||
# IO standard property
|
||||
elif isinstance(c, IOStandard):
|
||||
prop = ""
|
||||
valid = ['3.3_V_LVTTL_/_LVCMOS', '2.5_V_LVCMOS', '1.8_V_LVCMOS']
|
||||
if c.name in valid:
|
||||
prop = 'IO_STANDARD'
|
||||
|
||||
if prop == "":
|
||||
print("{} has a wrong IOStandard format [{}]".format(signame, c.name))
|
||||
print("Sould be selected from {}\n".format(valid))
|
||||
# Print error, warning ??
|
||||
return ""
|
||||
|
||||
tpl = 'design.set_property( "{signame}","{prop}","{val}")\n'
|
||||
return tpl.format(signame=signame, prop=prop, val=c.name)
|
||||
|
||||
# Others constraints
|
||||
elif isinstance(c, Misc):
|
||||
prop = ""
|
||||
if c.misc in ['WEAK_PULLUP', 'WEAK_PULLDOWN']:
|
||||
prop = 'PULL_OPTION'
|
||||
val = c.misc
|
||||
|
||||
if 'DRIVE_STRENGTH' in c.misc:
|
||||
prop = 'DRIVE_STRENGTH'
|
||||
val = c.misc.split('=')[1]
|
||||
|
||||
if prop == "":
|
||||
# Print error, warning ??
|
||||
return ""
|
||||
|
||||
tpl = 'design.set_property( "{signame}","{prop}","{val}")\n'
|
||||
return tpl.format(signame=signame, prop=prop, val=val)
|
||||
|
||||
def _format_conf_constraint(signame, pin, others, resname, fragment, platform):
|
||||
fmt_r = "{}:{}".format(*resname[:2])
|
||||
if resname[2] is not None:
|
||||
fmt_r += "." + resname[2]
|
||||
fmt_c = [_format_constraint(c, signame, fmt_r, fragment, platform) for c in ([Pins(pin)] + others)]
|
||||
return ''.join(fmt_c)
|
||||
|
||||
def _build_iface_gpio(named_sc, named_pc, fragment, platform, specials_gpios):
|
||||
conf = []
|
||||
inst = []
|
||||
|
||||
# GPIO
|
||||
for sig, pins, others, resname in named_sc:
|
||||
if sig not in specials_gpios:
|
||||
inst.append(_create_gpio_instance(fragment, platform, sig, pins))
|
||||
else:
|
||||
continue
|
||||
if len(pins) > 1:
|
||||
for i, p in enumerate(pins):
|
||||
conf.append(_format_conf_constraint("{}[{}]".format(sig, i), p, others, resname, fragment, platform))
|
||||
else:
|
||||
conf.append(_format_conf_constraint(sig, pins[0], others, resname, fragment, platform))
|
||||
if named_pc:
|
||||
conf.append("\n\n".join(named_pc))
|
||||
|
||||
conf = inst + conf
|
||||
|
||||
return "\n".join(conf)
|
||||
|
||||
def _build_peri(efinity_path, build_name, partnumber, named_sc, named_pc, fragment, platform, additional_iface_commands, specials_gpios):
|
||||
pythonpath = ""
|
||||
|
||||
header = platform.toolchain.ifacewriter.header(build_name, partnumber)
|
||||
gen = platform.toolchain.ifacewriter.generate()
|
||||
#TODO: move this to ifacewriter
|
||||
gpio = _build_iface_gpio(named_sc, named_pc, fragment, platform, specials_gpios)
|
||||
add = '\n'.join(additional_iface_commands)
|
||||
footer = platform.toolchain.ifacewriter.footer()
|
||||
|
||||
tools.write_to_file("iface.py", header + gen + gpio + add + footer)
|
||||
|
||||
subprocess.call([efinity_path + '/bin/python3', 'iface.py'])
|
||||
|
||||
# Project configuration ------------------------------------------------------------------------
|
||||
|
||||
def _build_xml(partnumber, build_name, sources, additional_xml_commands):
|
||||
|
||||
root = et.Element('efx:project')
|
||||
|
||||
now = datetime.datetime.now()
|
||||
date_str = " Date: " + now.strftime("%Y-%m-%d %H:%M") + " "
|
||||
|
||||
# Add the required attributes
|
||||
root.attrib['xmlns:efx'] = 'http://www.efinixinc.com/enf_proj'
|
||||
root.attrib['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
root.attrib['name'] = build_name
|
||||
root.attrib['description'] = ''
|
||||
root.attrib['last_change_date'] = date_str
|
||||
root.attrib['location'] = str(pathlib.Path().resolve())
|
||||
root.attrib['sw_version'] = '2021.1.165.2.19' # TODO: read it from sw_version.txt
|
||||
root.attrib['last_run_state'] = ''
|
||||
root.attrib['last_run_tool'] = ''
|
||||
root.attrib['last_run_flow'] = ''
|
||||
root.attrib['config_result_in_sync'] = 'sync'
|
||||
root.attrib['design_ood'] = 'sync'
|
||||
root.attrib['place_ood'] = 'sync'
|
||||
root.attrib['route_ood'] = 'sync'
|
||||
root.attrib['xsi:schemaLocation'] = 'http://www.efinixinc.com/enf_proj enf_proj.xsd'
|
||||
|
||||
device_info = et.SubElement(root, 'efx:device_info')
|
||||
et.SubElement(device_info, 'efx:family', name = 'Trion')
|
||||
et.SubElement(device_info, 'efx:device', name = partnumber)
|
||||
et.SubElement(device_info, 'efx:timing_model', name = 'C4')
|
||||
|
||||
design_info = et.SubElement(root, 'efx:design_info')
|
||||
et.SubElement(design_info, "efx:top_module", name = build_name)
|
||||
for filename, language, library in sources:
|
||||
if '.vh' not in filename:
|
||||
val = {'name':filename, 'version':'default', 'library':'default'}
|
||||
et.SubElement(design_info, "efx:design_file", val)
|
||||
et.SubElement(design_info, "efx:top_vhdl_arch", name = "")
|
||||
|
||||
constraint_info = et.SubElement(root, "efx:constraint_info")
|
||||
et.SubElement(constraint_info, "efx:sdc_file", name = "{}.sdc".format(build_name))
|
||||
|
||||
misc_info = et.SubElement(root, "efx:misc_info")
|
||||
ip_info = et.SubElement(root, "efx:ip_info")
|
||||
|
||||
synthesis = et.SubElement(root, "efx:synthesis", tool_name="efx_map")
|
||||
for l in additional_xml_commands:
|
||||
if l[0] == 'efx_map':
|
||||
val = {'name':l[1], 'value':l[2], 'value_type':l[3]}
|
||||
et.SubElement(synthesis, "efx:param", val)
|
||||
|
||||
place_and_route = et.SubElement(root, "efx:place_and_route", tool_name="efx_pnr")
|
||||
for l in additional_xml_commands:
|
||||
if l[0] == 'efx_pnr':
|
||||
val = {'name':l[1], 'value':l[2], 'value_type':l[3]}
|
||||
et.SubElement(place_and_route, "efx:param", val)
|
||||
|
||||
bitstream_generation = et.SubElement(root, "efx:bitstream_generation", tool_name="efx_pgm")
|
||||
for l in additional_xml_commands:
|
||||
if l[0] == 'efx_pgm':
|
||||
val = {'name':l[1], 'value':l[2], 'value_type':l[3]}
|
||||
et.SubElement(bitstream_generation, "efx:param", val)
|
||||
|
||||
xml_string = et.tostring(root, 'utf-8')
|
||||
reparsed = expatbuilder.parseString(xml_string, False)
|
||||
print_string = reparsed.toprettyxml(indent=" ")
|
||||
|
||||
# Generate .xml
|
||||
tools.write_to_file("{}.xml".format(build_name), print_string)
|
||||
|
||||
class EfinityToolchain():
|
||||
attr_translate = {}
|
||||
|
||||
def __init__(self, efinity_path):
|
||||
self.options = {}
|
||||
self.clocks = dict()
|
||||
self.false_paths = set()
|
||||
self.efinity_path = efinity_path
|
||||
self.additional_sdc_commands = []
|
||||
self.additional_xml_commands = []
|
||||
self.ifacewriter = InterfaceWriter(efinity_path)
|
||||
self.specials_gpios = []
|
||||
self.additional_iface_commands = []
|
||||
|
||||
def build(self, platform, fragment,
|
||||
build_dir = "build",
|
||||
build_name = "top",
|
||||
run = True,
|
||||
**kwargs):
|
||||
|
||||
self.ifacewriter.set_build_params(platform, build_name)
|
||||
|
||||
# Create build directory
|
||||
cwd = os.getcwd()
|
||||
os.makedirs(build_dir, exist_ok=True)
|
||||
os.chdir(build_dir)
|
||||
|
||||
# Finalize design
|
||||
if not isinstance(fragment, _Fragment):
|
||||
fragment = fragment.get_fragment()
|
||||
platform.finalize(fragment)
|
||||
|
||||
# Generate verilog
|
||||
v_output = platform.get_verilog(fragment, name=build_name, **kwargs)
|
||||
named_sc, named_pc = platform.resolve_signals(v_output.ns)
|
||||
v_file = build_name + ".v"
|
||||
v_output.write(v_file)
|
||||
platform.add_source(v_file)
|
||||
|
||||
sc = platform.constraint_manager.get_sig_constraints()
|
||||
self.specials_gpios = [(v_output.ns.get_name(sig)) for sig in self.specials_gpios]
|
||||
|
||||
if platform.verilog_include_paths:
|
||||
self.options['includ_path'] = '{' + ';'.join(platform.verilog_include_paths) + '}'
|
||||
|
||||
os.environ['EFXPT_HOME'] = self.efinity_path + '/pt'
|
||||
|
||||
# Generate design timing constraints file (.sdc)
|
||||
_build_sdc(
|
||||
clocks = self.clocks,
|
||||
false_paths = self.false_paths,
|
||||
vns = v_output.ns,
|
||||
named_sc = named_sc,
|
||||
build_name = build_name,
|
||||
additional_sdc_commands = self.additional_sdc_commands)
|
||||
|
||||
# Generate project file (.xml)
|
||||
_build_xml(
|
||||
partnumber = platform.device,
|
||||
build_name = build_name,
|
||||
sources = platform.sources,
|
||||
additional_xml_commands = self.additional_xml_commands)
|
||||
|
||||
# Generate constraints file (.peri.xml)
|
||||
_build_peri(
|
||||
efinity_path = self.efinity_path,
|
||||
build_name = build_name,
|
||||
partnumber = platform.device,
|
||||
named_sc = named_sc,
|
||||
named_pc = named_pc,
|
||||
fragment = fragment,
|
||||
platform = platform,
|
||||
additional_iface_commands = self.additional_iface_commands,
|
||||
specials_gpios = self.specials_gpios)
|
||||
|
||||
# DDR doesn't have Python API so we need to configure it
|
||||
# directly in the peri.xml file
|
||||
if self.ifacewriter.xml_blocks:
|
||||
self.ifacewriter.generate_xml_blocks()
|
||||
|
||||
# Run
|
||||
if run:
|
||||
subprocess.call([self.efinity_path + '/scripts/efx_run.py', build_name + '.xml', '-f', 'compile'])
|
||||
|
||||
os.chdir(cwd)
|
||||
|
||||
return v_output.ns
|
||||
|
||||
def add_period_constraint(self, platform, clk, period):
|
||||
clk.attr.add("keep")
|
||||
period = math.floor(period*1e3)/1e3 # round to lowest picosecond
|
||||
if clk in self.clocks:
|
||||
if period != self.clocks[clk]:
|
||||
raise ValueError("Clock already constrained to {:.2f}ns, new constraint to {:.2f}ns"
|
||||
.format(self.clocks[clk], period))
|
||||
self.clocks[clk] = period
|
||||
|
||||
def add_false_path_constraint(self, platform, from_, to):
|
||||
from_.attr.add("keep")
|
||||
to.attr.add("keep")
|
||||
if (to, from_) not in self.false_paths:
|
||||
self.false_paths.add((from_, to))
|
|
@ -0,0 +1,367 @@
|
|||
import os
|
||||
import csv
|
||||
import re
|
||||
import datetime
|
||||
|
||||
from xml.dom import expatbuilder
|
||||
import xml.etree.ElementTree as et
|
||||
|
||||
from litex.build import tools
|
||||
|
||||
namespaces = { 'efxpt' : 'http://www.efinixinc.com/peri_design_db',
|
||||
'xi' : 'http://www.w3.org/2001/XInclude'
|
||||
}
|
||||
|
||||
class InterfaceWriter():
|
||||
def __init__(self, efinity_path):
|
||||
self.efinity_path = efinity_path
|
||||
self.blocks = []
|
||||
self.xml_blocks = []
|
||||
self.filename = ''
|
||||
self.platform = None
|
||||
|
||||
def set_build_params(self, platform, build_name):
|
||||
self.filename = build_name
|
||||
self.platform = platform
|
||||
|
||||
def generate_xml_blocks(self):
|
||||
et.register_namespace('efxpt', "http://www.efinixinc.com/peri_design_db")
|
||||
tree = et.parse(self.filename + '.peri.xml')
|
||||
root = tree.getroot()
|
||||
|
||||
for block in self.xml_blocks:
|
||||
if block['type'] == 'DDR':
|
||||
self.add_ddr_xml(root, block)
|
||||
if block['type'] == 'LVDS':
|
||||
self.add_ddr_lvds(root, block)
|
||||
|
||||
xml_string = et.tostring(root, 'utf-8')
|
||||
reparsed = expatbuilder.parseString(xml_string, False)
|
||||
print_string = reparsed.toprettyxml(indent=" ")
|
||||
|
||||
# Remove lines with only whitespaces. Not sure why they are here
|
||||
print_string = os.linesep.join([s for s in print_string.splitlines() if s.strip()])
|
||||
|
||||
tools.write_to_file("{}.peri.xml".format(self.filename), print_string)
|
||||
|
||||
def add_ddr_lvds(self, root, params):
|
||||
lvds_info = root.find('efxpt:lvds_info', namespaces)
|
||||
if params['mode'] == 'OUTPUT':
|
||||
dir = 'tx'
|
||||
mode = 'out'
|
||||
else:
|
||||
dir = 'rx'
|
||||
mode = 'in'
|
||||
|
||||
pad = self.platform.parser.get_gpio_instance_from_pin(params['location'][0])
|
||||
pad = pad.replace('TXP', 'TX')
|
||||
pad = pad.replace('TXN', 'TX')
|
||||
pad = pad.replace('RXP', 'RX')
|
||||
pad = pad.replace('RXN', 'RX')
|
||||
# Sometimes there is an extra identifier at the end
|
||||
# TODO: do a better parser
|
||||
if pad.count('_') == 2:
|
||||
pad = pad.rsplit('_', 1)[0]
|
||||
|
||||
lvds = et.SubElement(lvds_info, 'efxpt:lvds',
|
||||
name = params['name'],
|
||||
lvds_def = pad,
|
||||
ops_type = dir)
|
||||
|
||||
et.SubElement(lvds, 'efxpt:ltx_info', pll_instance = '',
|
||||
fast_clock_name = '{}'.format(params['fast_clk']),
|
||||
slow_clock_name = '{}'.format(params['slow_clk']),
|
||||
reset_name = '',
|
||||
out_bname = '{}'.format(params['name']),
|
||||
oe_name = '',
|
||||
clock_div = '1',
|
||||
mode = '{}'.format(mode),
|
||||
serialization = '{}'.format(params['serialisation']),
|
||||
reduced_swing = 'false',
|
||||
load = '3')
|
||||
|
||||
|
||||
def add_ddr_xml(self, root, params):
|
||||
ddr_info = root.find('efxpt:ddr_info', namespaces)
|
||||
|
||||
ddr = et.SubElement(ddr_info, 'efxpt:ddr',
|
||||
name = 'ddr_inst1',
|
||||
ddr_def = 'DDR_0',
|
||||
cs_preset_id = '173',
|
||||
cs_mem_type = 'LPDDR3',
|
||||
cs_ctrl_width = 'x32',
|
||||
cs_dram_width = 'x32',
|
||||
cs_dram_density = '8G',
|
||||
cs_speedbin = '800',
|
||||
target0_enable = 'true',
|
||||
target1_enable = 'false',
|
||||
ctrl_type = 'none')
|
||||
|
||||
axi_suffix = '' # '_1' for second port
|
||||
type_suffix = '_0' # '_1' for second port
|
||||
|
||||
gen_pin_target0 = et.SubElement(ddr, 'efxpt:gen_pin_target0')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wdata{}'.format(axi_suffix), type_name='WDATA{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wready{}'.format(axi_suffix), type_name='WREADY{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wid{}'.format(axi_suffix), type_name='WID{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_bready{}'.format(axi_suffix), type_name='BREADY{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rdata{}'.format(axi_suffix), type_name='RDATA{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_aid{}'.format(axi_suffix), type_name='AID{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_bvalid{}'.format(axi_suffix), type_name='BVALID{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rlast{}'.format(axi_suffix), type_name='RLAST{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_bid{}'.format(axi_suffix), type_name='BID{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_asize{}'.format(axi_suffix), type_name='ASIZE{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_atype{}'.format(axi_suffix), type_name='ATYPE{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_aburst{}'.format(axi_suffix), type_name='ABURST{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wvalid{}'.format(axi_suffix), type_name='WVALID{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wlast{}'.format(axi_suffix), type_name='WLAST{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_aaddr{}'.format(axi_suffix), type_name='AADDR{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rid{}'.format(axi_suffix), type_name='RID{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_avalid{}'.format(axi_suffix), type_name='AVALID{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rvalid{}'.format(axi_suffix), type_name='RVALID{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_alock{}'.format(axi_suffix), type_name='ALOCK{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rready{}'.format(axi_suffix), type_name='RREADY{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_rresp{}'.format(axi_suffix), type_name='RRESP{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_wstrb{}'.format(axi_suffix), type_name='WSTRB{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_aready{}'.format(axi_suffix), type_name='AREADY{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_alen{}'.format(axi_suffix), type_name='ALEN{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target0, 'efxpt:pin', name='axi_clk', type_name='ACLK{}'.format(type_suffix), is_bus = 'false', is_clk = 'true', is_clk_invert = 'false')
|
||||
|
||||
axi_suffix = '_1' # '_1' for second port
|
||||
type_suffix = '_1' # '_1' for second port
|
||||
|
||||
gen_pin_target1 = et.SubElement(ddr, 'efxpt:gen_pin_target1')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_wdata{}'.format(axi_suffix), type_name='WDATA{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_wready{}'.format(axi_suffix), type_name='WREADY{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_wid{}'.format(axi_suffix), type_name='WID{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_bready{}'.format(axi_suffix), type_name='BREADY{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_rdata{}'.format(axi_suffix), type_name='RDATA{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_aid{}'.format(axi_suffix), type_name='AID{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_bvalid{}'.format(axi_suffix), type_name='BVALID{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_rlast{}'.format(axi_suffix), type_name='RLAST{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_bid{}'.format(axi_suffix), type_name='BID{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_asize{}'.format(axi_suffix), type_name='ASIZE{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_atype{}'.format(axi_suffix), type_name='ATYPE{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_aburst{}'.format(axi_suffix), type_name='ABURST{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_wvalid{}'.format(axi_suffix), type_name='WVALID{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_wlast{}'.format(axi_suffix), type_name='WLAST{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_aaddr{}'.format(axi_suffix), type_name='AADDR{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_rid{}'.format(axi_suffix), type_name='RID{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_avalid{}'.format(axi_suffix), type_name='AVALID{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_rvalid{}'.format(axi_suffix), type_name='RVALID{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_alock{}'.format(axi_suffix), type_name='ALOCK{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_rready{}'.format(axi_suffix), type_name='RREADY{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_rresp{}'.format(axi_suffix), type_name='RRESP{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_wstrb{}'.format(axi_suffix), type_name='WSTRB{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_aready{}'.format(axi_suffix), type_name='AREADY{}'.format(type_suffix), is_bus = 'false')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_alen{}'.format(axi_suffix), type_name='ALEN{}'.format(type_suffix), is_bus = 'true')
|
||||
et.SubElement(gen_pin_target1, 'efxpt:pin', name='axi_clk', type_name='ACLK{}'.format(type_suffix), is_bus = 'false', is_clk = 'true', is_clk_invert = 'false')
|
||||
|
||||
gen_pin_config = et.SubElement(ddr, 'efxpt:gen_pin_config')
|
||||
et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SEQ_RST', is_bus = 'false')
|
||||
et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SCL_IN', is_bus = 'false')
|
||||
et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SEQ_START', is_bus = 'false')
|
||||
et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='RSTN', is_bus = 'false')
|
||||
et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SDA_IN', is_bus = 'false')
|
||||
et.SubElement(gen_pin_config, 'efxpt:pin', name='', type_name='CFG_SDA_OEN', is_bus = 'false')
|
||||
|
||||
cs_fpga = et.SubElement(ddr, 'efxpt:cs_fpga')
|
||||
et.SubElement(cs_fpga, 'efxpt:param', name='FPGA_ITERM', value='120', value_type = 'str')
|
||||
et.SubElement(cs_fpga, 'efxpt:param', name='FPGA_OTERM', value='34', value_type = 'str')
|
||||
|
||||
cs_memory = et.SubElement(ddr, 'efxpt:cs_memory')
|
||||
et.SubElement(cs_memory, 'efxpt:param', name='RTT_NOM', value='RZQ/2', value_type = 'str')
|
||||
et.SubElement(cs_memory, 'efxpt:param', name='MEM_OTERM', value='40', value_type = 'str')
|
||||
et.SubElement(cs_memory, 'efxpt:param', name='CL', value='RL=6/WL=3', value_type = 'str')
|
||||
|
||||
timing = et.SubElement(ddr, 'efxpt:cs_memory_timing')
|
||||
et.SubElement(timing, 'efxpt:param', name='tRAS', value= '42.000', value_type='float')
|
||||
et.SubElement(timing, 'efxpt:param', name='tRC', value= '60.000', value_type='float')
|
||||
et.SubElement(timing, 'efxpt:param', name='tRP', value= '18.000', value_type='float')
|
||||
et.SubElement(timing, 'efxpt:param', name='tRCD', value= '18.000', value_type='float')
|
||||
et.SubElement(timing, 'efxpt:param', name='tREFI', value= '3.900', value_type='float')
|
||||
et.SubElement(timing, 'efxpt:param', name='tRFC', value= '210.000', value_type='float')
|
||||
et.SubElement(timing, 'efxpt:param', name='tRTP', value= '10.000', value_type='float')
|
||||
et.SubElement(timing, 'efxpt:param', name='tWTR', value= '10.000', value_type='float')
|
||||
et.SubElement(timing, 'efxpt:param', name='tRRD', value= '10.000', value_type='float')
|
||||
et.SubElement(timing, 'efxpt:param', name='tFAW', value= '50.000', value_type='float')
|
||||
|
||||
cs_control = et.SubElement(ddr, 'efxpt:cs_control')
|
||||
et.SubElement(cs_control, 'efxpt:param', name='AMAP', value= 'ROW-COL_HIGH-BANK-COL_LOW', value_type='str')
|
||||
et.SubElement(cs_control, 'efxpt:param', name='EN_AUTO_PWR_DN', value= 'Off', value_type='str')
|
||||
et.SubElement(cs_control, 'efxpt:param', name='EN_AUTO_SELF_REF', value= 'No', value_type='str')
|
||||
|
||||
cs_gate_delay = et.SubElement(ddr, 'efxpt:cs_gate_delay')
|
||||
et.SubElement(cs_gate_delay, 'efxpt:param', name='EN_DLY_OVR', value= 'No', value_type='str')
|
||||
et.SubElement(cs_gate_delay, 'efxpt:param', name='GATE_C_DLY', value= '3', value_type='int')
|
||||
et.SubElement(cs_gate_delay, 'efxpt:param', name='GATE_F_DLY', value= '0', value_type='int')
|
||||
|
||||
def header(self, build_name, partnumber):
|
||||
header = "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision()
|
||||
header += """
|
||||
import os
|
||||
import sys
|
||||
import pprint
|
||||
|
||||
home = '{0}'
|
||||
|
||||
os.environ['EFXPT_HOME'] = home + '/pt'
|
||||
os.environ['EFXPGM_HOME'] = home + '/pgm'
|
||||
os.environ['EFXDBG_HOME'] = home + '/debugger'
|
||||
os.environ['EFXIPM_HOME'] = home + '/ipm'
|
||||
|
||||
sys.path.append(home + '/pt/bin')
|
||||
sys.path.append(home + '/lib/python3.8/site-packages')
|
||||
|
||||
from api_service.design import DesignAPI
|
||||
from api_service.device import DeviceAPI
|
||||
|
||||
is_verbose = {1}
|
||||
|
||||
design = DesignAPI(is_verbose)
|
||||
device = DeviceAPI(is_verbose)
|
||||
|
||||
design.create('{2}', '{3}', './../gateware', overwrite=True)
|
||||
|
||||
"""
|
||||
return header.format(self.efinity_path, 'True', build_name, partnumber)
|
||||
|
||||
def get_block(self, name):
|
||||
for b in self.blocks:
|
||||
if b['name'] == name:
|
||||
return b
|
||||
return None
|
||||
|
||||
def generate_gpio(self, block, verbose=True):
|
||||
name = block['name']
|
||||
mode = block['mode']
|
||||
cmd = ''
|
||||
|
||||
if mode == 'INOUT':
|
||||
if len(block['location']) == 1:
|
||||
cmd += 'design.create_inout_gpio("{}")\n'.format(name)
|
||||
cmd += 'design.assign_pkg_pin("{}","{}")\n'.format(name, block['location'][0])
|
||||
else:
|
||||
cmd += 'design.create_inout_gpio("{}",{},0)\n'.format(name, block['size']-1)
|
||||
for i, pad in enumerate(block['location']):
|
||||
cmd += 'design.assign_pkg_pin("{}[{}]","{}")\n'.format(name, i, pad)
|
||||
cmd += '\n'
|
||||
return cmd
|
||||
|
||||
if mode == 'INPUT':
|
||||
if len(block['location']) == 1:
|
||||
cmd += 'design.create_input_gpio("{}")\n'.format(name)
|
||||
cmd += 'design.assign_pkg_pin("{}","{}")\n'.format(name, block['location'][0])
|
||||
else:
|
||||
cmd += 'design.create_input_gpio("{}",{},0)\n'.format(name, block['size']-1)
|
||||
for i, pad in enumerate(block['location']):
|
||||
cmd += 'design.assign_pkg_pin("{}[{}]","{}")\n'.format(name, i, pad)
|
||||
if 'in_reg' in block:
|
||||
cmd += 'design.set_property("{}","IN_REG","{}")\n'.format(name, block['in_reg'])
|
||||
cmd += 'design.set_property("{}","IN_CLK_PIN","{}")\n'.format(name, block['in_clk_pin'])
|
||||
return cmd
|
||||
|
||||
if mode == 'OUTPUT':
|
||||
if len(block['location']) == 1:
|
||||
cmd += 'design.create_output_gpio("{}")\n'.format(name)
|
||||
cmd += 'design.assign_pkg_pin("{}","{}")\n'.format(name, block['location'][0])
|
||||
else:
|
||||
cmd += 'design.create_input_gpio("{}",{},0)\n'.format(name, block['size']-1)
|
||||
for i, pad in enumerate(block['location']):
|
||||
cmd += 'design.assign_pkg_pin("{}[{}]","{}")\n'.format(name, i, pad)
|
||||
|
||||
if 'out_reg' in block:
|
||||
cmd += 'design.set_property("{}","OUT_REG","{}")\n'.format(name, block['out_reg'])
|
||||
cmd += 'design.set_property("{}","OUT_CLK_PIN","{}")\n'.format(name, block['out_clk_pin'])
|
||||
|
||||
if 'drive_strength' in block:
|
||||
cmd += 'design.set_property("{}","DRIVE_STRENGTH","4")\n'.format(name, block['drive_strength'])
|
||||
|
||||
cmd += '\n'
|
||||
return cmd
|
||||
|
||||
if mode == 'INPUT_CLK':
|
||||
cmd += 'design.create_input_clock_gpio("{}")\n'.format(name)
|
||||
cmd += 'design.set_property("{}","IN_PIN","{}")\n'.format(name, name)
|
||||
cmd += 'design.assign_pkg_pin("{}","{}")\n\n'.format(name, block['location'])
|
||||
return cmd
|
||||
|
||||
if mode == 'OUTPUT_CLK':
|
||||
cmd += 'design.create_clockout_gpio("{}")\n'.format(name)
|
||||
cmd += 'design.set_property("{}","OUT_CLK_PIN","{}")\n'.format(name, name)
|
||||
cmd += 'design.assign_pkg_pin("{}","{}")\n\n'.format(name, block['location'])
|
||||
return cmd
|
||||
|
||||
cmd = '# TODO: ' + str(block) +'\n'
|
||||
return cmd
|
||||
|
||||
def generate_pll(self, block, verbose=True):
|
||||
name = block['name']
|
||||
cmd = '# ---------- PLL {} ---------\n'.format(name)
|
||||
cmd += 'design.create_block("{}", block_type="PLL")\n'.format(name)
|
||||
cmd += 'pll_config = {{ "REFCLK_FREQ":"{}" }}\n'.format(block['input_freq'] / 1e6)
|
||||
cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name)
|
||||
|
||||
if block['input_clock'] == 'EXTERNAL':
|
||||
cmd += 'design.gen_pll_ref_clock("{}", pll_res="{}", refclk_src="{}", refclk_name="{}", ext_refclk_no="{}")\n\n' \
|
||||
.format(name, block['resource'], block['input_clock'], block['input_clock_name'], block['clock_no'])
|
||||
else:
|
||||
cmd += 'design.gen_pll_ref_clock("{}", pll_res="{}", refclk_name="{}", refclk_src="CORE")\n'.format(name, block['resource'], block['input_signal'])
|
||||
cmd += 'design.set_property("{}", "CORE_CLK_PIN", "{}", block_type="PLL")\n\n'.format(name, block['input_signal'])
|
||||
|
||||
cmd += 'design.set_property("{}","LOCKED_PIN","{}", block_type="PLL")\n'.format(name, block['locked'])
|
||||
if block['reset'] != '':
|
||||
cmd += 'design.set_property("{}","RSTN_PIN","{}", block_type="PLL")\n\n'.format(name, block['reset'])
|
||||
|
||||
# Output clock 0 is enabled by default
|
||||
for i, clock in enumerate(block['clk_out']):
|
||||
if i > 0:
|
||||
cmd += 'pll_config = {{ "CLKOUT{}_EN":"1", "CLKOUT{}_PIN":"{}" }}\n'.format(i, i, clock[0])
|
||||
else:
|
||||
cmd += 'pll_config = {{ "CLKOUT{}_PIN":"{}" }}\n'.format(i, clock[0])
|
||||
|
||||
cmd += 'design.set_property("{}", pll_config, block_type="PLL")\n\n'.format(name)
|
||||
|
||||
for i, clock in enumerate(block['clk_out']):
|
||||
cmd += 'design.set_property("{}","CLKOUT{}_PHASE","{}","PLL")\n'.format(name, i, clock[2])
|
||||
|
||||
cmd += 'target_freq = {\n'
|
||||
for i, clock in enumerate(block['clk_out']):
|
||||
cmd += ' "CLKOUT{}_FREQ": "{}",\n'.format(i, clock[1] / 1e6)
|
||||
cmd += '}\n'
|
||||
cmd += 'calc_result = design.auto_calc_pll_clock("{}", target_freq)\n'.format(name)
|
||||
|
||||
if 'extra' in block:
|
||||
cmd += block['extra']
|
||||
cmd += '\n'
|
||||
|
||||
if verbose:
|
||||
cmd += 'print("#### {} ####")\n'.format(name)
|
||||
cmd += 'clksrc_info = design.trace_ref_clock("{}", block_type="PLL")\n'.format(name)
|
||||
cmd += 'pprint.pprint(clksrc_info)\n'
|
||||
cmd += 'clock_source_prop = ["REFCLK_SOURCE", "CORE_CLK_PIN", "EXT_CLK", "CLKOUT1_EN", "CLKOUT2_EN","REFCLK_FREQ", "RESOURCE"]\n'
|
||||
cmd += 'clock_source_prop += ["CLKOUT0_FREQ", "CLKOUT1_FREQ", "CLKOUT2_FREQ"]\n'
|
||||
cmd += 'clock_source_prop += ["CLKOUT0_PHASE", "CLKOUT1_PHASE", "CLKOUT2_PHASE"]\n'
|
||||
cmd += 'prop_map = design.get_property("{}", clock_source_prop, block_type="PLL")\n'.format(name)
|
||||
cmd += 'pprint.pprint(prop_map)\n'
|
||||
|
||||
cmd += '# ---------- END PLL {} ---------\n\n'.format(name)
|
||||
return cmd
|
||||
|
||||
def generate(self):
|
||||
output = ''
|
||||
for b in self.blocks:
|
||||
if b['type'] == 'PLL':
|
||||
output += self.generate_pll(b)
|
||||
if b['type'] == 'GPIO':
|
||||
output += self.generate_gpio(b)
|
||||
|
||||
return output
|
||||
|
||||
def footer(self):
|
||||
return """
|
||||
# Check design, generate constraints and reports
|
||||
design.generate(enable_bitstream=True)
|
||||
# Save the configured periphery design
|
||||
design.save()"""
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
#
|
||||
# This file is part of LiteX.
|
||||
#
|
||||
# Copyright (c) 2021 Franck Jullien <franck.jullien@collshade.fr>
|
||||
# Copyright (c) 2015-2018 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
import os
|
||||
|
||||
from litex.build.generic_platform import *
|
||||
from litex.build.efinix import common, efinity
|
||||
from litex.build.efinix import EfinixDbParser
|
||||
|
||||
# EfinixPlatform -----------------------------------------------------------------------------------
|
||||
|
||||
class EfinixPlatform(GenericPlatform):
|
||||
bitstream_ext = ".bit"
|
||||
|
||||
def __init__(self, *args, toolchain="efinity", **kwargs):
|
||||
GenericPlatform.__init__(self, *args, **kwargs)
|
||||
|
||||
self.pll_available = ['PLL_TL0', 'PLL_TR0', 'PLL_TR1', 'PLL_TR2', 'PLL_TR3', 'PLL_BR0', 'PLL_BR1', 'PLL_BR2', 'PLL_BL0']
|
||||
self.pll_used = []
|
||||
|
||||
if 'LITEX_ENV_EFINITY' in os.environ:
|
||||
self.efinity_path = os.environ['LITEX_ENV_EFINITY'].rstrip('/')
|
||||
os.environ['EFINITY_HOME'] = self.efinity_path
|
||||
else:
|
||||
raise OSError('Unable to find Efinity toolchain, please set LITEX_ENV_EFINITY to ${install_dir}')
|
||||
|
||||
if toolchain == "efinity":
|
||||
self.toolchain = efinity.EfinityToolchain(self.efinity_path)
|
||||
else:
|
||||
raise ValueError("Unknown toolchain")
|
||||
|
||||
self.parser = EfinixDbParser(self.efinity_path, self.device)
|
||||
|
||||
def get_verilog(self, *args, special_overrides=dict(), **kwargs):
|
||||
so = dict(common.efinix_special_overrides)
|
||||
so.update(special_overrides)
|
||||
return GenericPlatform.get_verilog(self, *args, special_overrides=so,
|
||||
attr_translate=self.toolchain.attr_translate, **kwargs)
|
||||
|
||||
def build(self, *args, **kwargs):
|
||||
return self.toolchain.build(self, *args, **kwargs)
|
||||
|
||||
def add_period_constraint(self, clk, period):
|
||||
if clk is None: return
|
||||
if hasattr(clk, "p"):
|
||||
clk = clk.p
|
||||
self.toolchain.add_period_constraint(self, clk, period)
|
||||
|
||||
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)
|
||||
|
||||
# TODO: fix this when pin is like p = platform.request("sdios")
|
||||
# get_pin_location(p[1])
|
||||
# not tested with subsignal like get_pin_location(p.clk)
|
||||
def get_pin_location(self, sig):
|
||||
if sig is None:
|
||||
return None
|
||||
sc = self.constraint_manager.get_sig_constraints()
|
||||
for s, pins, others, resource in sc:
|
||||
if (s == sig) and (pins[0] != 'X'):
|
||||
return pins
|
||||
return None
|
||||
|
||||
def get_pin_name(self, sig):
|
||||
if sig is None:
|
||||
return None
|
||||
sc = self.constraint_manager.get_sig_constraints()
|
||||
for s, pins, others, resource in sc:
|
||||
if s == sig:
|
||||
if resource[2]:
|
||||
return resource[0] + '_' + resource[2]
|
||||
else:
|
||||
return resource[0]
|
||||
return None
|
||||
|
||||
def get_sig_constraint(self, sig):
|
||||
sc = self.constraint_manager.get_sig_constraints()
|
||||
for s, pins, others, resource in sc:
|
||||
if s == sig:
|
||||
return sc
|
||||
return None
|
||||
|
||||
def add_iface_io(self, name, size=1, append=True):
|
||||
self.add_extension([(name, 0, Pins(size))])
|
||||
tmp = self.request(name)
|
||||
# We don't want this IO to be in the interface configuration file as a simple GPIO
|
||||
if append:
|
||||
self.toolchain.specials_gpios.append(tmp)
|
||||
return tmp
|
||||
|
||||
def add_iface_ios(self, io, append=True):
|
||||
self.add_extension(io)
|
||||
tmp = self.request(io[0][0])
|
||||
if append:
|
||||
for s in tmp.flatten():
|
||||
self.toolchain.specials_gpios.append(s)
|
||||
return tmp
|
||||
|
||||
def del_record_signal(self, record, sig):
|
||||
for pos, (name, item) in enumerate(vars(record).items()):
|
||||
if isinstance(item, Signal):
|
||||
if item == sig:
|
||||
# Two first pos are name and layout
|
||||
del record.layout[pos-2]
|
||||
delattr(record, name)
|
||||
break
|
||||
|
||||
def get_pll_resource(self, name):
|
||||
self.pll_used.append(name)
|
||||
self.pll_available.remove(name)
|
||||
print('Pll used : ' + str(self.pll_used))
|
||||
print('Pll pll_available: ' + str(self.pll_available))
|
||||
|
||||
def get_free_pll_resource(self):
|
||||
return self.pll_available[0]
|
|
@ -0,0 +1,32 @@
|
|||
#
|
||||
# This file is part of LiteX.
|
||||
#
|
||||
# Copyright (c) 2021 Franck Jullien <franck.jullien@collshade.fr>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
from litex.build.generic_programmer import GenericProgrammer
|
||||
|
||||
class EfinixProgrammer(GenericProgrammer):
|
||||
|
||||
def __init__(self, cable_name=""):
|
||||
self.cable_name = cable_name
|
||||
if 'LITEX_ENV_EFINITY' in os.environ:
|
||||
self.efinity_path = os.environ['LITEX_ENV_EFINITY'].rstrip('/')
|
||||
os.environ['EFINITY_HOME'] = self.efinity_path
|
||||
else:
|
||||
raise OSError('Unable to find Efinity toolchain, please set LITEX_ENV_EFINITY to ${install_dir}')
|
||||
|
||||
def load_bitstream(self, bitstream_file, cable_suffix=""):
|
||||
os.environ['EFXPGM_HOME'] = self.efinity_path + '/pgm'
|
||||
if (subprocess.call([self.efinity_path + '/bin/python3', self.efinity_path +
|
||||
'/pgm/bin/efx_pgm/ftdi_program.py', bitstream_file,
|
||||
"-m", "jtag"], env=os.environ.copy()) != 0):
|
||||
msg = f"Error occured during {self.__class__.__name__}'s call, please check:\n"
|
||||
msg += f"- {self.__class__.__name__} installation.\n"
|
||||
msg += f"- access permissions.\n"
|
||||
msg += f"- hardware and cable."
|
||||
raise OSError(msg)
|
|
@ -0,0 +1,220 @@
|
|||
#
|
||||
# This file is part of LiteEth.
|
||||
#
|
||||
# Copyright (c) 2021 Franck Jullien <franck.jullien@collshade.fr>
|
||||
# Copyright (c) 2015-2020 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
# RGMII PHY for 7-Series Xilinx FPGA
|
||||
|
||||
from migen import *
|
||||
from migen.genlib.resetsync import AsyncResetSynchronizer
|
||||
|
||||
from litex.build.generic_platform import *
|
||||
from litex.soc.cores.clock import *
|
||||
|
||||
from liteeth.common import *
|
||||
from liteeth.phy.common import *
|
||||
|
||||
class LiteEthPHYRGMIITX(Module):
|
||||
def __init__(self, platform, pads):
|
||||
self.sink = sink = stream.Endpoint(eth_phy_description(8))
|
||||
|
||||
# # #
|
||||
|
||||
name = platform.get_pin_name(pads.tx_data)
|
||||
pad = platform.get_pin_location(pads.tx_data)
|
||||
name = 'auto_' + name
|
||||
tx_data_h = []
|
||||
tx_data_l = []
|
||||
|
||||
# This a workaround, we could use signals with 4 bits but there is
|
||||
# a problem with the Python API that prevents it
|
||||
for i in range(4):
|
||||
tx_data_h.append(platform.add_iface_io(name + str(i) + '_HI'))
|
||||
tx_data_l.append(platform.add_iface_io(name + str(i) + '_LO'))
|
||||
|
||||
block = {'type':'GPIO',
|
||||
'mode':'OUTPUT',
|
||||
'name':name + str(i),
|
||||
'location':[pad[i]],
|
||||
'size':1,
|
||||
'out_reg':'DDIO_RESYNC',
|
||||
'out_clk_pin':'auto_eth_tx_clk',
|
||||
'is_inclk_inverted':False,
|
||||
'drive_strength':4 # TODO: get this from pin constraints
|
||||
}
|
||||
platform.toolchain.ifacewriter.blocks.append(block)
|
||||
|
||||
platform.del_record_signal(pads, pads.tx_data)
|
||||
|
||||
name = platform.get_pin_name(pads.tx_ctl)
|
||||
pad = platform.get_pin_location(pads.tx_ctl)
|
||||
name = 'auto_' + name
|
||||
tx_ctl_h = platform.add_iface_io(name + '_HI')
|
||||
tx_ctl_l = platform.add_iface_io(name + '_LO')
|
||||
|
||||
block = {'type':'GPIO',
|
||||
'mode':'OUTPUT',
|
||||
'name':name,
|
||||
'location':[pad[0]],
|
||||
'size':1,
|
||||
'out_reg':'DDIO_RESYNC',
|
||||
'out_clk_pin':'auto_eth_tx_clk',
|
||||
'is_inclk_inverted':False,
|
||||
'drive_strength':4 # TODO: get this from pin constraints
|
||||
}
|
||||
platform.toolchain.ifacewriter.blocks.append(block)
|
||||
platform.del_record_signal(pads, pads.tx_ctl)
|
||||
|
||||
self.sync += [ tx_data_h[0].eq(sink.data[0]),
|
||||
tx_data_h[1].eq(sink.data[1]),
|
||||
tx_data_h[2].eq(sink.data[2]),
|
||||
tx_data_h[3].eq(sink.data[3]),
|
||||
tx_data_l[0].eq(sink.data[4]),
|
||||
tx_data_l[1].eq(sink.data[5]),
|
||||
tx_data_l[2].eq(sink.data[6]),
|
||||
tx_data_l[3].eq(sink.data[7]),
|
||||
tx_ctl_h.eq(sink.valid),
|
||||
tx_ctl_l.eq(sink.valid),
|
||||
]
|
||||
|
||||
self.comb += sink.ready.eq(1)
|
||||
|
||||
class LiteEthPHYRGMIIRX(Module):
|
||||
def __init__(self, platform, pads):
|
||||
self.source = source = stream.Endpoint(eth_phy_description(8))
|
||||
|
||||
# # #
|
||||
|
||||
rx_data = Signal(8)
|
||||
|
||||
# Add a DDIO_RESYNC input block with 'auto_eth_rx_clk' as clock
|
||||
name = platform.get_pin_name(pads.rx_data)
|
||||
pad = platform.get_pin_location(pads.rx_data)
|
||||
name = 'auto_' + name
|
||||
rx_data_h = []
|
||||
rx_data_l = []
|
||||
# This a workaround, we could use signals with 4 bits but there is
|
||||
# a problem with the Python API that prevents it
|
||||
for i in range(4):
|
||||
rx_data_h.append(platform.add_iface_io(name + str(i) + '_HI'))
|
||||
rx_data_l.append(platform.add_iface_io(name + str(i) + '_LO'))
|
||||
|
||||
block = {'type':'GPIO',
|
||||
'mode':'INPUT',
|
||||
'name':name + str(i),
|
||||
'location':[pad[i]],
|
||||
'size':1,
|
||||
'in_reg':'DDIO_RESYNC',
|
||||
'in_clk_pin':'auto_eth_rx_clk',
|
||||
'is_inclk_inverted':False
|
||||
}
|
||||
platform.toolchain.ifacewriter.blocks.append(block)
|
||||
|
||||
platform.del_record_signal(pads, pads.rx_data)
|
||||
|
||||
self.comb += rx_data.eq(Cat(rx_data_l[0], rx_data_l[1], rx_data_l[2], rx_data_l[3],
|
||||
rx_data_h[0], rx_data_h[1], rx_data_h[2], rx_data_h[3]))
|
||||
|
||||
rx_ctl_d = Signal()
|
||||
self.sync += rx_ctl_d.eq(pads.rx_ctl)
|
||||
|
||||
last = Signal()
|
||||
self.comb += last.eq(~pads.rx_ctl & rx_ctl_d)
|
||||
self.sync += [
|
||||
source.valid.eq(rx_ctl_d),
|
||||
source.data.eq(rx_data),
|
||||
source.last.eq(last),
|
||||
]
|
||||
|
||||
class LiteEthPHYRGMIICRG(Module, AutoCSR):
|
||||
def __init__(self, platform, clock_pads, with_hw_init_reset, tx_delay=2e-9, hw_reset_cycles=256):
|
||||
self._reset = CSRStorage()
|
||||
|
||||
# # #
|
||||
|
||||
# Clocks
|
||||
|
||||
self.clock_domains.cd_eth_rx = ClockDomain()
|
||||
self.clock_domains.cd_eth_tx = ClockDomain()
|
||||
|
||||
# *************************
|
||||
# * RX CLOCK *
|
||||
# *************************
|
||||
|
||||
# Add a GPIO block with clock input property
|
||||
# Add a input 'auto_eth_rx_clk' to the top.v
|
||||
clkrx = platform.add_iface_io('auto_eth_rx_clk')
|
||||
block = {'type':'GPIO',
|
||||
'size':1,
|
||||
# Get the location from the original resource
|
||||
'location': platform.get_pin_location(clock_pads.rx)[0],
|
||||
'name':platform.get_pin_name(clkrx),
|
||||
'mode':'INPUT_CLK'
|
||||
}
|
||||
platform.toolchain.ifacewriter.blocks.append(block)
|
||||
self.comb += self.cd_eth_rx.clk.eq(clkrx)
|
||||
|
||||
cmd = "create_clock -period {} auto_eth_rx_clk".format(1e9/125e6)
|
||||
platform.toolchain.additional_sdc_commands.append(cmd)
|
||||
|
||||
# *************************
|
||||
# * TX CLOCK PIN *
|
||||
# *************************
|
||||
|
||||
block = {'type':'GPIO',
|
||||
'size':1,
|
||||
# Get the location from the original resource
|
||||
'location': platform.get_pin_location(clock_pads.tx)[0],
|
||||
'name':'auto_eth_tx_delayed_clk',
|
||||
'mode':'OUTPUT_CLK'
|
||||
}
|
||||
platform.toolchain.ifacewriter.blocks.append(block)
|
||||
|
||||
# *************************
|
||||
# * TX CLOCK *
|
||||
# *************************
|
||||
|
||||
self.submodules.pll = pll = TRIONPLL(platform)
|
||||
pll.register_clkin(None, 125e6, name='auto_eth_rx_clk')
|
||||
pll.create_clkout(None, 125e6, phase=0, name='auto_eth_tx_delayed_clk')
|
||||
pll.create_clkout(self.cd_eth_tx, 125e6, name='auto_eth_tx_clk')
|
||||
|
||||
cmd = "create_clock -period {} auto_eth_tx_clk".format(1e9/125e6)
|
||||
platform.toolchain.additional_sdc_commands.append(cmd)
|
||||
|
||||
platform.delete(clock_pads)
|
||||
|
||||
# *************************
|
||||
# * RESET *
|
||||
# *************************
|
||||
|
||||
self.reset = reset = Signal()
|
||||
if with_hw_init_reset:
|
||||
self.submodules.hw_reset = LiteEthPHYHWReset(cycles=hw_reset_cycles)
|
||||
self.comb += reset.eq(self._reset.storage | self.hw_reset.reset)
|
||||
else:
|
||||
self.comb += reset.eq(self._reset.storage)
|
||||
if hasattr(clock_pads, "rst_n"):
|
||||
self.comb += clock_pads.rst_n.eq(~reset)
|
||||
self.specials += [
|
||||
AsyncResetSynchronizer(self.cd_eth_tx, reset),
|
||||
AsyncResetSynchronizer(self.cd_eth_rx, reset),
|
||||
]
|
||||
|
||||
#platform.add_false_path_constraints(ClockSignal('sys'), self.cd_eth_rx.clk, self.cd_eth_tx.clk)
|
||||
|
||||
class LiteEthPHYRGMII(Module, AutoCSR):
|
||||
dw = 8
|
||||
tx_clk_freq = 125e6
|
||||
rx_clk_freq = 125e6
|
||||
def __init__(self, platform, clock_pads, pads, with_hw_init_reset=True, tx_delay=2e-9, rx_delay=2e-9,
|
||||
iodelay_clk_freq=200e6, hw_reset_cycles=256):
|
||||
self.submodules.crg = LiteEthPHYRGMIICRG(platform, clock_pads, with_hw_init_reset, tx_delay, hw_reset_cycles)
|
||||
self.submodules.tx = ClockDomainsRenamer("eth_tx")(LiteEthPHYRGMIITX(platform, pads))
|
||||
self.submodules.rx = ClockDomainsRenamer("eth_rx")(LiteEthPHYRGMIIRX(platform, pads))
|
||||
self.sink, self.source = self.tx.sink, self.rx.source
|
||||
|
||||
#if hasattr(pads, "mdc"):
|
||||
# self.submodules.mdio = LiteEthPHYMDIO(pads)
|
|
@ -0,0 +1,448 @@
|
|||
#
|
||||
# This file is part of LiteX.
|
||||
#
|
||||
# Copyright (c) 2021 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
import os
|
||||
import math
|
||||
|
||||
from migen import *
|
||||
from migen.genlib.cdc import MultiReg
|
||||
|
||||
from litex.soc.interconnect.csr import *
|
||||
from litex.soc.interconnect import stream
|
||||
from litex.soc.cores.code_tmds import TMDSEncoder
|
||||
|
||||
from litex.build.io import SDROutput, DDROutput
|
||||
|
||||
# Video Constants ----------------------------------------------------------------------------------
|
||||
|
||||
hbits = 12
|
||||
vbits = 12
|
||||
|
||||
# Video Timings ------------------------------------------------------------------------------------
|
||||
|
||||
video_timings = {
|
||||
"640x480@60Hz" : {
|
||||
"pix_clk" : 25.175e6,
|
||||
"h_active" : 640,
|
||||
"h_blanking" : 160,
|
||||
"h_sync_offset" : 16,
|
||||
"h_sync_width" : 96,
|
||||
"v_active" : 480,
|
||||
"v_blanking" : 45,
|
||||
"v_sync_offset" : 10,
|
||||
"v_sync_width" : 2,
|
||||
},
|
||||
"640x480@75Hz" : {
|
||||
"pix_clk" : 31.5e6,
|
||||
"h_active" : 640,
|
||||
"h_blanking" : 200,
|
||||
"h_sync_offset" : 16,
|
||||
"h_sync_width" : 64,
|
||||
"v_active" : 480,
|
||||
"v_blanking" : 20,
|
||||
"v_sync_offset" : 1,
|
||||
"v_sync_width" : 3,
|
||||
},
|
||||
"800x600@60Hz" : {
|
||||
"pix_clk" : 40e6,
|
||||
"h_active" : 800,
|
||||
"h_blanking" : 256,
|
||||
"h_sync_offset" : 40,
|
||||
"h_sync_width" : 128,
|
||||
"v_active" : 600,
|
||||
"v_blanking" : 28,
|
||||
"v_sync_offset" : 1,
|
||||
"v_sync_width" : 4,
|
||||
},
|
||||
"800x600@75Hz": {
|
||||
"pix_clk" : 49.5e6,
|
||||
"h_active" : 800,
|
||||
"h_blanking" : 256,
|
||||
"h_sync_offset" : 16,
|
||||
"h_sync_width" : 80,
|
||||
"v_active" : 600,
|
||||
"v_blanking" : 25,
|
||||
"v_sync_offset" : 1,
|
||||
"v_sync_width" : 3,
|
||||
},
|
||||
"1024x768@60Hz": {
|
||||
"pix_clk" : 65e6,
|
||||
"h_active" : 1024,
|
||||
"h_blanking" : 320,
|
||||
"h_sync_offset" : 24,
|
||||
"h_sync_width" : 136,
|
||||
"v_active" : 768,
|
||||
"v_blanking" : 38,
|
||||
"v_sync_offset" : 3,
|
||||
"v_sync_width" : 6,
|
||||
},
|
||||
"1024x768@75Hz": {
|
||||
"pix_clk" : 78.8e6,
|
||||
"h_active" : 1024,
|
||||
"h_blanking" : 288,
|
||||
"h_sync_offset" : 16,
|
||||
"h_sync_width" : 96,
|
||||
"v_active" : 768,
|
||||
"v_blanking" : 32,
|
||||
"v_sync_offset" : 1,
|
||||
"v_sync_width" : 3,
|
||||
},
|
||||
"1280x720@60Hz": {
|
||||
"pix_clk" : 74.25e6,
|
||||
"h_active" : 1280,
|
||||
"h_blanking" : 370,
|
||||
"h_sync_offset" : 220,
|
||||
"h_sync_width" : 40,
|
||||
"v_active" : 720,
|
||||
"v_blanking" : 30,
|
||||
"v_sync_offset" : 5,
|
||||
"v_sync_width" : 5,
|
||||
},
|
||||
"1920x1080@30Hz": {
|
||||
"pix_clk" : 89.01e6,
|
||||
"h_active" : 1920,
|
||||
"h_blanking" : 720,
|
||||
"h_sync_offset" : 528,
|
||||
"h_sync_width" : 44,
|
||||
"v_active" : 1080,
|
||||
"v_blanking" : 45,
|
||||
"v_sync_offset" : 4,
|
||||
"v_sync_width" : 5,
|
||||
},
|
||||
"1920x1080@60Hz": {
|
||||
"pix_clk" : 148.35e6,
|
||||
"h_active" : 1920,
|
||||
"h_blanking" : 280,
|
||||
"h_sync_offset" : 88,
|
||||
"h_sync_width" : 44,
|
||||
"v_active" : 1080,
|
||||
"v_blanking" : 45,
|
||||
"v_sync_offset" : 4,
|
||||
"v_sync_width" : 5,
|
||||
},
|
||||
"1920x1080@50Hz": {
|
||||
"pix_clk" : 148.5e6,
|
||||
"h_active" : 1920,
|
||||
"h_blanking" : 720,
|
||||
"h_sync_offset" : 528,
|
||||
"h_sync_width" : 44,
|
||||
"v_active" : 1080,
|
||||
"v_blanking" : 45,
|
||||
"v_sync_offset" : 4,
|
||||
"v_sync_width" : 5,
|
||||
},
|
||||
"1920x1200@60Hz": {
|
||||
"pix_clk" : 148.2e6,
|
||||
"h_active" : 1920,
|
||||
"h_blanking" : 80,
|
||||
"h_sync_offset" : 8,
|
||||
"h_sync_width" : 32,
|
||||
"v_active" : 1200,
|
||||
"v_blanking" : 35,
|
||||
"v_sync_offset" : 21,
|
||||
"v_sync_width" : 8,
|
||||
},
|
||||
}
|
||||
|
||||
# Video Timing Generator ---------------------------------------------------------------------------
|
||||
|
||||
video_timing_layout = [
|
||||
# Synchronization signals.
|
||||
("hsync", 1),
|
||||
("vsync", 1),
|
||||
("de", 1),
|
||||
# Extended/Optional synchronization signals.
|
||||
("hres", hbits),
|
||||
("vres", vbits),
|
||||
("hcount", hbits),
|
||||
("vcount", vbits),
|
||||
]
|
||||
|
||||
video_data_layout = [
|
||||
# Synchronization signals.
|
||||
("hsync", 1),
|
||||
("vsync", 1),
|
||||
("de", 1),
|
||||
# Data signals.
|
||||
("r", 8),
|
||||
("g", 8),
|
||||
("b", 8),
|
||||
]
|
||||
|
||||
video_data_layout_dual = [
|
||||
# Synchronization signals.
|
||||
("hsync", 2),
|
||||
("vsync", 2),
|
||||
("de", 2),
|
||||
# Data signals.
|
||||
("r", 16),
|
||||
("g", 16),
|
||||
("b", 16),
|
||||
]
|
||||
|
||||
class VideoTimingGenerator(Module, AutoCSR):
|
||||
def __init__(self, default_video_timings="800x600@60Hz"):
|
||||
# Check / Get Video Timings (can be str or dict)
|
||||
if isinstance(default_video_timings, str):
|
||||
try:
|
||||
self.video_timings = vt = video_timings[default_video_timings]
|
||||
except KeyError:
|
||||
msg = [f"Video Timings {default_video_timings} not supported, availables:"]
|
||||
for video_timing in video_timings.keys():
|
||||
msg.append(f" - {video_timing} / {video_timings[video_timing]['pix_clk']/1e6:3.2f}MHz.")
|
||||
raise ValueError("\n".join(msg))
|
||||
else:
|
||||
self.video_timings = vt = default_video_timings
|
||||
|
||||
# MMAP Control/Status Registers.
|
||||
self._enable = CSRStorage(reset=1)
|
||||
|
||||
self._hres = CSRStorage(hbits, vt["h_active"])
|
||||
self._hsync_start = CSRStorage(hbits, vt["h_active"] + vt["h_sync_offset"])
|
||||
self._hsync_end = CSRStorage(hbits, vt["h_active"] + vt["h_sync_offset"] + vt["h_sync_width"])
|
||||
self._hscan = CSRStorage(hbits, vt["h_active"] + vt["h_blanking"])
|
||||
|
||||
self._vres = CSRStorage(vbits, vt["v_active"])
|
||||
self._vsync_start = CSRStorage(vbits, vt["v_active"] + vt["v_sync_offset"])
|
||||
self._vsync_end = CSRStorage(vbits, vt["v_active"] + vt["v_sync_offset"] + vt["v_sync_width"])
|
||||
self._vscan = CSRStorage(vbits, vt["v_active"] + vt["v_blanking"])
|
||||
|
||||
# Video Timing Source
|
||||
self.source = source = stream.Endpoint(video_timing_layout)
|
||||
|
||||
# # #
|
||||
|
||||
# Resynchronize Enable to Video clock domain.
|
||||
self.enable = enable = Signal()
|
||||
self.specials += MultiReg(self._enable.storage, enable)
|
||||
|
||||
# Resynchronize Horizontal Timings to Video clock domain.
|
||||
self.hres = hres = Signal(hbits)
|
||||
self.hsync_start = hsync_start = Signal(hbits)
|
||||
self.hsync_end = hsync_end = Signal(hbits)
|
||||
self.hscan = hscan = Signal(hbits)
|
||||
self.specials += MultiReg(self._hres.storage, hres)
|
||||
self.specials += MultiReg(self._hsync_start.storage, hsync_start)
|
||||
self.specials += MultiReg(self._hsync_end.storage, hsync_end)
|
||||
self.specials += MultiReg(self._hscan.storage, hscan)
|
||||
|
||||
# Resynchronize Vertical Timings to Video clock domain.
|
||||
self.vres = vres = Signal(vbits)
|
||||
self.vsync_start = vsync_start = Signal(vbits)
|
||||
self.vsync_end = vsync_end = Signal(vbits)
|
||||
self.vscan = vscan = Signal(vbits)
|
||||
self.specials += MultiReg(self._vres.storage, vres)
|
||||
self.specials += MultiReg(self._vsync_start.storage, vsync_start)
|
||||
self.specials += MultiReg(self._vsync_end.storage, vsync_end)
|
||||
self.specials += MultiReg(self._vscan.storage, vscan)
|
||||
|
||||
# Generate timings.
|
||||
hactive = Signal()
|
||||
vactive = Signal()
|
||||
fsm = FSM(reset_state="IDLE")
|
||||
fsm = ResetInserter()(fsm)
|
||||
self.submodules.fsm = fsm
|
||||
self.comb += fsm.reset.eq(~enable)
|
||||
fsm.act("IDLE",
|
||||
NextValue(hactive, 0),
|
||||
NextValue(vactive, 0),
|
||||
NextValue(source.hres, hres),
|
||||
NextValue(source.vres, vres),
|
||||
NextValue(source.hcount, 0),
|
||||
NextValue(source.vcount, 0),
|
||||
NextState("RUN")
|
||||
)
|
||||
self.comb += source.de.eq(hactive & vactive) # DE when both HActive and VActive.
|
||||
self.sync += source.first.eq((source.hcount == 0) & (source.vcount == 0)),
|
||||
self.sync += source.last.eq( (source.hcount == hscan) & (source.vcount == vscan)),
|
||||
fsm.act("RUN",
|
||||
source.valid.eq(1),
|
||||
If(source.ready,
|
||||
# Increment HCount.
|
||||
NextValue(source.hcount, source.hcount + 1),
|
||||
# Generate HActive / HSync.
|
||||
If(source.hcount == 0, NextValue(hactive, 1)), # Start of HActive.
|
||||
If(source.hcount == hres, NextValue(hactive, 0)), # End of HActive.
|
||||
If(source.hcount == hsync_start, NextValue(source.hsync, 1)), # Start of HSync.
|
||||
If(source.hcount == hsync_end, NextValue(source.hsync, 0)), # End of HSync.
|
||||
# End of HScan.
|
||||
If(source.hcount == hscan,
|
||||
# Reset HCount.
|
||||
NextValue(source.hcount, 0),
|
||||
# Increment VCount.
|
||||
NextValue(source.vcount, source.vcount + 1),
|
||||
# Generate VActive / VSync.
|
||||
If(source.vcount == 0, NextValue(vactive, 1)), # Start of VActive.
|
||||
If(source.vcount == vres, NextValue(vactive, 0)), # End of HActive.
|
||||
If(source.vcount == vsync_start, NextValue(source.vsync, 1)), # Start of VSync.
|
||||
If(source.vcount == vsync_end, NextValue(source.vsync, 0)), # End of VSync.
|
||||
# End of VScan.
|
||||
If(source.vcount == vscan,
|
||||
# Reset VCount.
|
||||
NextValue(source.vcount, 0),
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Video Patterns -----------------------------------------------------------------------------------
|
||||
|
||||
class ColorBarsPattern(Module):
|
||||
"""Color Bars Pattern"""
|
||||
def __init__(self):
|
||||
self.enable = Signal(reset=1)
|
||||
self.vtg_sink = vtg_sink = stream.Endpoint(video_timing_layout)
|
||||
self.source = source = stream.Endpoint(video_data_layout)
|
||||
|
||||
# # #
|
||||
|
||||
enable = Signal()
|
||||
self.specials += MultiReg(self.enable, enable)
|
||||
|
||||
# Control Path.
|
||||
pix = Signal(hbits)
|
||||
bar = Signal(3)
|
||||
|
||||
fsm = FSM(reset_state="IDLE")
|
||||
fsm = ResetInserter()(fsm)
|
||||
self.submodules.fsm = fsm
|
||||
self.comb += fsm.reset.eq(~self.enable)
|
||||
fsm.act("IDLE",
|
||||
NextValue(pix, 0),
|
||||
NextValue(bar, 0),
|
||||
vtg_sink.ready.eq(1),
|
||||
If(vtg_sink.valid & vtg_sink.first & (vtg_sink.hcount == 0) & (vtg_sink.vcount == 0),
|
||||
vtg_sink.ready.eq(0),
|
||||
NextState("RUN")
|
||||
)
|
||||
)
|
||||
fsm.act("RUN",
|
||||
vtg_sink.connect(source, keep={"valid", "ready", "last", "de", "hsync", "vsync"}),
|
||||
If(source.valid & source.ready & source.de,
|
||||
NextValue(pix, pix + 1),
|
||||
If(pix == (vtg_sink.hres[3:] -1), # 8 Color Bars.
|
||||
NextValue(pix, 0),
|
||||
NextValue(bar, bar + 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Data Path.
|
||||
color_bar = [
|
||||
# R G B
|
||||
[0xff, 0xff, 0xff], # White
|
||||
[0xff, 0xff, 0x00], # Yellow
|
||||
[0x00, 0xff, 0xff], # Cyan
|
||||
[0x00, 0xff, 0x00], # Green
|
||||
[0xff, 0x00, 0xff], # Purple
|
||||
[0xff, 0x00, 0x00], # Red
|
||||
[0x00, 0x00, 0xff], # Blue
|
||||
[0x00, 0x00, 0x00], # Black
|
||||
]
|
||||
cases = {}
|
||||
for i in range(8):
|
||||
cases[i] = [
|
||||
source.r.eq(color_bar[i][0]),
|
||||
source.g.eq(color_bar[i][1]),
|
||||
source.b.eq(color_bar[i][2])
|
||||
]
|
||||
self.comb += Case(bar, cases)
|
||||
|
||||
class VideoLVDSPHY(Module):
|
||||
def __init__(self, platform, pads, pixel_clk='px_clk', px_clk_x3_5='px_clk_x3_5'):
|
||||
self.sink = sink = stream.Endpoint(video_data_layout_dual)
|
||||
|
||||
# # #
|
||||
|
||||
channel1 = Record(video_data_layout)
|
||||
channel2 = Record(video_data_layout)
|
||||
|
||||
self.comb += [ channel1.r.eq(sink.r[0:8]),
|
||||
channel1.g.eq(sink.g[0:8]),
|
||||
channel1.b.eq(sink.b[0:8]),
|
||||
channel1.de.eq(sink.de[0]),
|
||||
channel1.vsync.eq(sink.vsync[0]),
|
||||
channel1.hsync.eq(sink.hsync[0]),
|
||||
|
||||
channel2.r.eq(sink.r[7:16]),
|
||||
channel2.g.eq(sink.g[7:16]),
|
||||
channel2.b.eq(sink.b[7:16]),
|
||||
channel2.de.eq(sink.de[1]),
|
||||
channel2.vsync.eq(sink.vsync[1]),
|
||||
channel2.hsync.eq(sink.hsync[1]),
|
||||
]
|
||||
|
||||
# Always ack Sink, no backpressure.
|
||||
self.comb += sink.ready.eq(1)
|
||||
|
||||
# -------- LVDS CLOCK -----------
|
||||
|
||||
name = platform.get_pin_name(pads.clk)
|
||||
pad = platform.get_pin_location(pads.clk)
|
||||
|
||||
clk = platform.add_iface_io('hdmi_clk', 7)
|
||||
|
||||
block = {'type':'LVDS',
|
||||
'mode':'OUTPUT',
|
||||
'name':name,
|
||||
'location':[pad[0]],
|
||||
'serialisation':7,
|
||||
'fast_clk':px_clk_x3_5,
|
||||
'slow_clk':pixel_clk,
|
||||
|
||||
}
|
||||
platform.toolchain.ifacewriter.xml_blocks.append(block)
|
||||
platform.delete(pads.clk)
|
||||
|
||||
# -------- LVDS DATA -----------
|
||||
|
||||
lvds = []
|
||||
|
||||
lvds.append((pads.rxpa1, platform.get_pin_name(pads.rxpa1), platform.get_pin_location(pads.rxpa1)))
|
||||
lvds.append((pads.rxpa2, platform.get_pin_name(pads.rxpa2), platform.get_pin_location(pads.rxpa2)))
|
||||
rxpa1 = platform.add_iface_io('hdmi_rxpa1', 7)
|
||||
rxpa2 = platform.add_iface_io('hdmi_rxpa2', 7)
|
||||
|
||||
lvds.append((pads.rxpb1, platform.get_pin_name(pads.rxpb1), platform.get_pin_location(pads.rxpb1)))
|
||||
lvds.append((pads.rxpb2, platform.get_pin_name(pads.rxpb2), platform.get_pin_location(pads.rxpb2)))
|
||||
rxpb1 = platform.add_iface_io('hdmi_rxpb1', 7)
|
||||
rxpb2 = platform.add_iface_io('hdmi_rxpb2', 7)
|
||||
|
||||
lvds.append((pads.rxpc1, platform.get_pin_name(pads.rxpc1), platform.get_pin_location(pads.rxpc1)))
|
||||
lvds.append((pads.rxpc2, platform.get_pin_name(pads.rxpc2), platform.get_pin_location(pads.rxpc2)))
|
||||
rxpc1 = platform.add_iface_io('hdmi_rxpc1', 7)
|
||||
rxpc2 = platform.add_iface_io('hdmi_rxpc2', 7)
|
||||
|
||||
lvds.append((pads.rxpd1, platform.get_pin_name(pads.rxpd1), platform.get_pin_location(pads.rxpd1)))
|
||||
lvds.append((pads.rxpd2, platform.get_pin_name(pads.rxpd2), platform.get_pin_location(pads.rxpd2)))
|
||||
rxpd1 = platform.add_iface_io('hdmi_rxpd1', 7)
|
||||
rxpd2 = platform.add_iface_io('hdmi_rxpd2', 7)
|
||||
|
||||
for signal, name, pad in lvds:
|
||||
block = {'type':'LVDS',
|
||||
'mode':'OUTPUT',
|
||||
'name':name,
|
||||
'location':[pad[0]],
|
||||
'serialisation':7,
|
||||
'fast_clk':px_clk_x3_5,
|
||||
'slow_clk':pixel_clk,
|
||||
|
||||
}
|
||||
platform.toolchain.ifacewriter.xml_blocks.append(block)
|
||||
platform.delete(signal)
|
||||
|
||||
self.comb += [ rxpa1.eq(Cat(channel1.g[2], channel1.r[7], channel1.r[6], channel1.r[5], channel1.r[4], channel1.r[3], channel1.r[2])),
|
||||
rxpb1.eq(Cat(channel1.b[3], channel1.b[2], channel1.g[7], channel1.g[6], channel1.g[5], channel1.g[4], channel1.g[3])),
|
||||
rxpc1.eq(Cat(channel1.de, channel1.vsync, channel1.hsync, channel1.b[7], channel1.b[6], channel1.b[5], channel1.b[4])),
|
||||
rxpd1.eq(Cat(0, channel1.b[7], channel1.b[7], channel1.g[7], channel1.g[7], channel1.r[7], channel1.r[7])),
|
||||
|
||||
rxpa2.eq(Cat(channel2.g[2], channel2.r[7], channel2.r[6], channel2.r[5], channel2.r[4], channel2.r[3], channel2.r[2])),
|
||||
rxpb2.eq(Cat(channel2.b[3], channel2.b[2], channel2.g[7], channel2.g[6], channel2.g[5], channel2.g[4], channel2.g[3])),
|
||||
rxpc2.eq(Cat(channel2.de, channel2.vsync, channel2.hsync, channel2.b[7], channel2.b[6], channel2.b[5], channel2.b[4])),
|
||||
rxpd2.eq(Cat(0, channel2.b[7], channel2.b[7], channel2.g[7], channel2.g[7], channel2.r[7], channel2.r[7])),
|
||||
|
||||
clk.eq(0b1100011),
|
||||
]
|
|
@ -190,6 +190,22 @@ class ConstraintManager:
|
|||
def add_extension(self, io):
|
||||
self.available.extend(io)
|
||||
|
||||
def delete(self, signal):
|
||||
for res, obj in self.matched:
|
||||
if isinstance(obj, Record):
|
||||
for pos, (name, item) in enumerate(vars(obj).items()):
|
||||
if isinstance(item, Signal):
|
||||
if item == signal:
|
||||
# Two first pos are name and layout
|
||||
del obj.layout[pos-2]
|
||||
delattr(obj, name)
|
||||
if len(obj.layout) == 0:
|
||||
self.matched.remove((res, obj))
|
||||
break
|
||||
else:
|
||||
if obj == signal:
|
||||
self.matched.remove((res, obj))
|
||||
|
||||
def request(self, name, number=None, loose=False):
|
||||
resource = _lookup(self.available, name, number, loose)
|
||||
if resource is None:
|
||||
|
@ -275,6 +291,8 @@ class ConstraintManager:
|
|||
if has_subsignals:
|
||||
for element in resource[2:]:
|
||||
if isinstance(element, Subsignal):
|
||||
# Because we could have removed one Signal From the record
|
||||
if hasattr(obj, element.name):
|
||||
sig = getattr(obj, element.name)
|
||||
pins, others = _separate_pins(top_constraints +
|
||||
element.constraints)
|
||||
|
@ -313,6 +331,9 @@ class GenericPlatform:
|
|||
def request(self, *args, **kwargs):
|
||||
return self.constraint_manager.request(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
return self.constraint_manager.delete(*args, **kwargs)
|
||||
|
||||
def request_all(self, *args, **kwargs):
|
||||
return self.constraint_manager.request_all(*args, **kwargs)
|
||||
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
from migen.fhdl.structure import *
|
||||
from migen.fhdl.module import *
|
||||
from migen.fhdl.bitcontainer import bits_for
|
||||
from migen.fhdl.tools import *
|
||||
from migen.fhdl.tracer import get_obj_var_name
|
||||
from migen.fhdl.verilog import _printexpr as verilog_printexpr
|
||||
from migen.fhdl.specials import Special, _MemoryPort, _MemoryLocation
|
||||
|
||||
(READ_FIRST, WRITE_FIRST, NO_CHANGE) = range(3)
|
||||
|
||||
class Memory(Special):
|
||||
def __init__(self, width, depth, init=None, name=None):
|
||||
Special.__init__(self)
|
||||
self.width = width
|
||||
self.depth = depth
|
||||
self.ports = []
|
||||
self.init = init
|
||||
self.name_override = get_obj_var_name(name, "mem")
|
||||
|
||||
def __getitem__(self, index):
|
||||
# simulation only
|
||||
return _MemoryLocation(self, index)
|
||||
|
||||
def get_port(self, write_capable=False, async_read=False,
|
||||
has_re=False, we_granularity=0, mode=WRITE_FIRST,
|
||||
clock_domain="sys"):
|
||||
if we_granularity >= self.width:
|
||||
we_granularity = 0
|
||||
adr = Signal(max=self.depth)
|
||||
dat_r = Signal(self.width)
|
||||
if write_capable:
|
||||
if we_granularity:
|
||||
we = Signal(self.width//we_granularity)
|
||||
else:
|
||||
we = Signal()
|
||||
dat_w = Signal(self.width)
|
||||
else:
|
||||
we = None
|
||||
dat_w = None
|
||||
if has_re:
|
||||
re = Signal()
|
||||
else:
|
||||
re = None
|
||||
mp = _MemoryPort(adr, dat_r, we, dat_w,
|
||||
async_read, re, we_granularity, mode,
|
||||
clock_domain)
|
||||
self.ports.append(mp)
|
||||
return mp
|
||||
|
||||
@staticmethod
|
||||
def emit_verilog(memory, ns, add_data_file):
|
||||
r = ""
|
||||
def gn(e):
|
||||
if isinstance(e, Memory):
|
||||
return ns.get_name(e)
|
||||
else:
|
||||
return verilog_printexpr(ns, e)[0]
|
||||
adrbits = bits_for(memory.depth-1)
|
||||
|
||||
for i in range(memory.width // 8):
|
||||
r += "reg [" + str((memory.width//4)-1) + ":0] " \
|
||||
+ gn(memory) + '_' + str(i) \
|
||||
+ "[0:" + str(memory.depth-1) + "];\n"
|
||||
|
||||
adr_regs = {}
|
||||
data_regs = {}
|
||||
for port in memory.ports:
|
||||
if not port.async_read:
|
||||
if port.mode == WRITE_FIRST:
|
||||
adr_reg = Signal(name_override="memadr")
|
||||
r += "reg [" + str(adrbits-1) + ":0] " \
|
||||
+ gn(adr_reg) + ";\n"
|
||||
adr_regs[id(port)] = adr_reg
|
||||
else:
|
||||
data_reg = Signal(name_override="memdat")
|
||||
r += "reg [" + str(memory.width-1) + ":0] " \
|
||||
+ gn(data_reg) + ";\n"
|
||||
data_regs[id(port)] = data_reg
|
||||
|
||||
for port in memory.ports:
|
||||
r += "always @(posedge " + gn(port.clock) + ") begin\n"
|
||||
if port.we is not None:
|
||||
if port.we_granularity:
|
||||
n = memory.width//port.we_granularity
|
||||
for i in range(n):
|
||||
if (i > 0):
|
||||
r += "always @(posedge " + gn(port.clock) + ") begin\n"
|
||||
m = i*port.we_granularity
|
||||
M = (i+1)*port.we_granularity-1
|
||||
sl = "[" + str(M) + ":" + str(m) + "]"
|
||||
r += "\tif (" + gn(port.we) + "[" + str(i) + "])\n"
|
||||
r += "\t\t" + gn(memory) + '_' + str(i) + "[" + gn(port.adr) + "]" + " <= " + gn(port.dat_w) + sl + ";\n"
|
||||
r += "end\n"
|
||||
else:
|
||||
r += "\tif (" + gn(port.we) + ")\n"
|
||||
r += "\t\t" + gn(memory) + "[" + gn(port.adr) + "] <= " + gn(port.dat_w) + ";\n"
|
||||
if not port.async_read:
|
||||
if port.mode == WRITE_FIRST:
|
||||
r += "always @(posedge " + gn(port.clock) + ") begin\n"
|
||||
rd = "\t" + gn(adr_regs[id(port)]) + " <= " + gn(port.adr) + ";\n"
|
||||
else:
|
||||
bassign = ""
|
||||
for i in range(memory.width // 8):
|
||||
m = i*port.we_granularity
|
||||
M = (i+1)*port.we_granularity-1
|
||||
sl = "[" + str(M) + ":" + str(m) + "]"
|
||||
bassign += gn(data_regs[id(port)]) + sl + " <= " + gn(memory) + "_" + str(i) + "[" + gn(port.adr) + "];\n"
|
||||
if port.mode == READ_FIRST:
|
||||
rd = "\t" + bassign
|
||||
elif port.mode == NO_CHANGE:
|
||||
rd = "\tif (!" + gn(port.we) + ")\n" \
|
||||
+ "\t\t" + bassign
|
||||
if port.re is None:
|
||||
r += rd
|
||||
else:
|
||||
r += "\tif (" + gn(port.re) + ")\n"
|
||||
r += "\t" + rd.replace("\n\t", "\n\t\t")
|
||||
r += "end\n\n"
|
||||
|
||||
for port in memory.ports:
|
||||
if port.async_read:
|
||||
r += "assign " + gn(port.dat_r) + " = " + gn(memory) + "[" + gn(port.adr) + "];\n"
|
||||
else:
|
||||
if port.mode == WRITE_FIRST:
|
||||
for i in range(memory.width // 8):
|
||||
m = i*port.we_granularity
|
||||
M = (i+1)*port.we_granularity-1
|
||||
sl = "[" + str(M) + ":" + str(m) + "]"
|
||||
r += "assign " + gn(port.dat_r) + sl + " = " + gn(memory) + "_" + str(i) + "[" + gn(adr_regs[id(port)]) + "];\n"
|
||||
else:
|
||||
r += "assign " + gn(port.dat_r) + " = " + gn(data_regs[id(port)]) + ";\n"
|
||||
r += "\n"
|
||||
|
||||
if memory.init is not None:
|
||||
content_7_0 = ""
|
||||
content_15_8 = ""
|
||||
content_23_16 = ""
|
||||
content_31_24 = ""
|
||||
formatter = "{:0" + str(int(memory.width / 4)) + "X}\n"
|
||||
|
||||
init_7_0 = []
|
||||
init_15_8 = []
|
||||
init_23_16 = []
|
||||
init_31_24 = []
|
||||
|
||||
for w in memory.init:
|
||||
init_7_0.append(w & 0xff)
|
||||
init_15_8.append((w >> 8) & 0xff)
|
||||
init_23_16.append((w >> 16) & 0xff)
|
||||
init_31_24.append((w >> 24) & 0xff)
|
||||
|
||||
for d in init_7_0:
|
||||
content_7_0 += formatter.format(d)
|
||||
|
||||
for d in init_15_8:
|
||||
content_15_8 += formatter.format(d)
|
||||
|
||||
for d in init_23_16:
|
||||
content_23_16 += formatter.format(d)
|
||||
|
||||
for d in init_31_24:
|
||||
content_31_24 += formatter.format(d)
|
||||
|
||||
memory_filename1 = add_data_file(gn(memory) + "1.init", content_7_0)
|
||||
memory_filename2 = add_data_file(gn(memory) + "2.init", content_15_8)
|
||||
memory_filename3 = add_data_file(gn(memory) + "3.init", content_23_16)
|
||||
memory_filename4 = add_data_file(gn(memory) + "4.init", content_31_24)
|
||||
r += "initial begin\n"
|
||||
r += "\t$readmemh(\"" + memory_filename1 + "\", " + gn(memory)+ "_0" + ");\n"
|
||||
r += "\t$readmemh(\"" + memory_filename2 + "\", " + gn(memory)+ "_1" + ");\n"
|
||||
r += "\t$readmemh(\"" + memory_filename3 + "\", " + gn(memory)+ "_2" + ");\n"
|
||||
r += "\t$readmemh(\"" + memory_filename4 + "\", " + gn(memory)+ "_3" + ");\n"
|
||||
r += "end\n\n"
|
||||
|
||||
return r
|
|
@ -14,3 +14,6 @@ from litex.soc.cores.clock.intel_cyclone10 import Cyclone10LPPLL
|
|||
from litex.soc.cores.clock.lattice_ice40 import iCE40PLL
|
||||
from litex.soc.cores.clock.lattice_ecp5 import ECP5PLL
|
||||
from litex.soc.cores.clock.lattice_nx import NXOSCA, NXPLL
|
||||
|
||||
# Efinix
|
||||
from litex.soc.cores.clock.efinix_trion import TRIONPLL
|
|
@ -0,0 +1,126 @@
|
|||
#
|
||||
# This file is part of LiteX.
|
||||
#
|
||||
# Copyright (c) 2021 Franck Jullien <franck.jullien@collshade.fr>
|
||||
# Copyright (c) 2021 Florent Kermarrec <florent@enjoy-digital.fr>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
from migen import *
|
||||
from migen.genlib.resetsync import AsyncResetSynchronizer
|
||||
|
||||
from litex.build.generic_platform import *
|
||||
from litex.soc.cores.clock.common import *
|
||||
|
||||
class Open(Signal): pass
|
||||
|
||||
#TODO: do somthing else
|
||||
count = 0
|
||||
|
||||
# Efinix / TRIONPLL ----------------------------------------------------------------------------------
|
||||
|
||||
class TRIONPLL(Module):
|
||||
nclkouts_max = 4
|
||||
def __init__(self, platform, with_reset=False):
|
||||
global count
|
||||
self.logger = logging.getLogger("TRIONPLL")
|
||||
self.logger.info("Creating TRIONPLL.".format())
|
||||
self.platform = platform
|
||||
self.nclkouts = 0
|
||||
self.pll_name = "pll{}".format(count)
|
||||
self.reset = Signal()
|
||||
self.locked = Signal()
|
||||
|
||||
block = {}
|
||||
count += 1
|
||||
|
||||
block['type'] = 'PLL'
|
||||
block['name'] = self.pll_name
|
||||
block['clk_out'] = []
|
||||
|
||||
pll_locked_name = self.pll_name + '_locked'
|
||||
block['locked'] = pll_locked_name
|
||||
io = self.platform.add_iface_io(pll_locked_name)
|
||||
self.comb += self.locked.eq(io)
|
||||
|
||||
block['reset'] = ''
|
||||
if with_reset:
|
||||
pll_reset_name = self.pll_name + '_reset'
|
||||
block['reset'] = pll_reset_name
|
||||
io = self.platform.add_iface_io(pll_reset_name)
|
||||
self.comb += io.eq(self.reset)
|
||||
|
||||
self.platform.toolchain.ifacewriter.blocks.append(block)
|
||||
|
||||
def register_clkin(self, clkin, freq, name= ''):
|
||||
block = self.platform.toolchain.ifacewriter.get_block(self.pll_name)
|
||||
|
||||
block['input_clock_name'] = self.platform.get_pin_name(clkin)
|
||||
|
||||
# If clkin has a pin number, PLL clock input is EXTERNAL
|
||||
if self.platform.get_pin_location(clkin):
|
||||
|
||||
pad_name = self.platform.get_pin_location(clkin)[0]
|
||||
self.platform.delete(clkin)
|
||||
|
||||
#tpl = "create_clock -name {clk} -period {period} [get_ports {{{clk}}}]"
|
||||
#sdc = self.platform.toolchain.additional_sdc_commands
|
||||
#sdc.append(tpl.format(clk=block['input_clock_name'], period=1/freq))
|
||||
|
||||
try:
|
||||
(pll_res, clock_no) = self.platform.parser.get_pll_inst_from_pin(pad_name)
|
||||
except:
|
||||
self.logger.error("Cannot find a pll with {} as input".format(pad_name))
|
||||
quit()
|
||||
|
||||
block['input_clock'] = 'EXTERNAL'
|
||||
block['resource'] = pll_res
|
||||
block['clock_no'] = clock_no
|
||||
self.logger.info("Clock source: {}, using EXT_CLK{}".format(block['input_clock'], clock_no))
|
||||
self.platform.get_pll_resource(pll_res)
|
||||
else:
|
||||
block['input_clock'] = 'INTERNAL'
|
||||
block['resource'] = self.platform.get_free_pll_resource()
|
||||
block['input_signal'] = name
|
||||
self.logger.info("Clock source: {}".format(block['input_clock']))
|
||||
|
||||
block['input_freq'] = freq
|
||||
|
||||
self.logger.info("Using {}".format(block['resource']))
|
||||
|
||||
def create_clkout(self, cd, freq, phase=0, margin=1e-2, name='', with_reset=False):
|
||||
assert self.nclkouts < self.nclkouts_max
|
||||
|
||||
if name != '':
|
||||
clk_out_name = name
|
||||
else:
|
||||
clk_out_name = '{}_CLKOUT{}'.format(self.pll_name, self.nclkouts)
|
||||
|
||||
if cd != None:
|
||||
self.platform.add_extension([(clk_out_name, 0, Pins(1))])
|
||||
tmp = self.platform.request(clk_out_name)
|
||||
|
||||
if with_reset:
|
||||
self.specials += AsyncResetSynchronizer(cd, ~self.locked)
|
||||
|
||||
# We don't want this IO to be in the interface configuration file as a simple GPIO
|
||||
self.platform.toolchain.specials_gpios.append(tmp)
|
||||
self.comb += cd.clk.eq(tmp)
|
||||
create_clkout_log(self.logger, cd.name, freq, margin, self.nclkouts)
|
||||
|
||||
self.nclkouts += 1
|
||||
|
||||
block = self.platform.toolchain.ifacewriter.get_block(self.pll_name)
|
||||
block['clk_out'].append([clk_out_name, freq, phase, margin])
|
||||
|
||||
def extra(self, extra):
|
||||
block = self.platform.toolchain.ifacewriter.get_block(self.pll_name)
|
||||
block['extra'] = extra
|
||||
|
||||
def compute_config(self):
|
||||
pass
|
||||
|
||||
def set_configuration(self):
|
||||
pass
|
||||
|
||||
def do_finalize(self):
|
||||
pass
|
|
@ -822,7 +822,7 @@ class SoC(Module):
|
|||
self.check_if_exists(name)
|
||||
setattr(self.submodules, name, SoCController(**kwargs))
|
||||
|
||||
def add_ram(self, name, origin, size, contents=[], mode="rw"):
|
||||
def add_ram(self, name, origin, size, contents=[], mode="rw", no_we=False):
|
||||
ram_cls = {
|
||||
"wishbone": wishbone.SRAM,
|
||||
"axi-lite": axi.AXILiteSRAM,
|
||||
|
@ -832,7 +832,7 @@ class SoC(Module):
|
|||
"axi-lite": axi.AXILiteInterface,
|
||||
}[self.bus.standard]
|
||||
ram_bus = interface_cls(data_width=self.bus.data_width)
|
||||
ram = ram_cls(size, bus=ram_bus, init=contents, read_only=(mode == "r"))
|
||||
ram = ram_cls(size, bus=ram_bus, init=contents, read_only=(mode == "r"), no_we=no_we)
|
||||
self.bus.add_slave(name, ram.bus, SoCRegion(origin=origin, size=size, mode=mode))
|
||||
self.check_if_exists(name)
|
||||
self.logger.info("RAM {} {} {}.".format(
|
||||
|
@ -841,8 +841,8 @@ class SoC(Module):
|
|||
self.bus.regions[name]))
|
||||
setattr(self.submodules, name, ram)
|
||||
|
||||
def add_rom(self, name, origin, size, contents=[], mode="r"):
|
||||
self.add_ram(name, origin, size, contents, mode=mode)
|
||||
def add_rom(self, name, origin, size, contents=[], mode="r", no_we=False):
|
||||
self.add_ram(name, origin, size, contents, mode=mode, no_we=no_we)
|
||||
|
||||
def init_rom(self, name, contents=[], auto_size=True):
|
||||
self.logger.info("Initializing ROM {} with contents (Size: {}).".format(
|
||||
|
|
|
@ -79,10 +79,12 @@ class SoCCore(LiteXSoC):
|
|||
integrated_rom_size = 0,
|
||||
integrated_rom_mode = "r",
|
||||
integrated_rom_init = [],
|
||||
integrated_rom_no_we = False,
|
||||
|
||||
# SRAM parameters
|
||||
integrated_sram_size = 0x2000,
|
||||
integrated_sram_init = [],
|
||||
integrated_sram_no_we = False,
|
||||
|
||||
# MAIN_RAM parameters
|
||||
integrated_main_ram_size = 0,
|
||||
|
@ -197,11 +199,11 @@ class SoCCore(LiteXSoC):
|
|||
|
||||
# Add integrated ROM
|
||||
if integrated_rom_size:
|
||||
self.add_rom("rom", self.cpu.reset_address, integrated_rom_size, integrated_rom_init, integrated_rom_mode)
|
||||
self.add_rom("rom", self.cpu.reset_address, integrated_rom_size, integrated_rom_init, integrated_rom_mode, no_we=integrated_rom_no_we)
|
||||
|
||||
# Add integrated SRAM
|
||||
if integrated_sram_size:
|
||||
self.add_ram("sram", self.mem_map["sram"], integrated_sram_size)
|
||||
self.add_ram("sram", self.mem_map["sram"], integrated_sram_size, no_we=integrated_sram_no_we)
|
||||
|
||||
# Add integrated MAIN_RAM (only useful when no external SRAM/SDRAM is available)
|
||||
if integrated_main_ram_size:
|
||||
|
|
|
@ -783,7 +783,7 @@ class AXILite2CSR(Module):
|
|||
# AXILite SRAM -------------------------------------------------------------------------------------
|
||||
|
||||
class AXILiteSRAM(Module):
|
||||
def __init__(self, mem_or_size, read_only=None, init=None, bus=None):
|
||||
def __init__(self, mem_or_size, read_only=None, init=None, bus=None, no_we=False):
|
||||
if bus is None:
|
||||
bus = AXILiteInterface()
|
||||
self.bus = bus
|
||||
|
|
|
@ -329,7 +329,7 @@ class Converter(Module):
|
|||
# Wishbone SRAM ------------------------------------------------------------------------------------
|
||||
|
||||
class SRAM(Module):
|
||||
def __init__(self, mem_or_size, read_only=None, init=None, bus=None):
|
||||
def __init__(self, mem_or_size, read_only=None, init=None, bus=None, no_we=False):
|
||||
if bus is None:
|
||||
bus = Interface()
|
||||
self.bus = bus
|
||||
|
@ -337,8 +337,13 @@ class SRAM(Module):
|
|||
if isinstance(mem_or_size, Memory):
|
||||
assert(mem_or_size.width <= bus_data_width)
|
||||
self.mem = mem_or_size
|
||||
else:
|
||||
if no_we:
|
||||
from litex.gen.fhdl.memory import Memory as NoWeMemory
|
||||
self.mem = NoWeMemory(bus_data_width, mem_or_size//(bus_data_width//8), init=init)
|
||||
else:
|
||||
self.mem = Memory(bus_data_width, mem_or_size//(bus_data_width//8), init=init)
|
||||
|
||||
if read_only is None:
|
||||
if hasattr(self.mem, "bus_read_only"):
|
||||
read_only = self.mem.bus_read_only
|
||||
|
|
Loading…
Reference in New Issue