mirror of
https://github.com/enjoy-digital/litex.git
synced 2025-01-04 09:52:26 -05:00
introduce conversion output object (prevents file IO in FHDL backends)
This commit is contained in:
parent
8ce683964a
commit
e1702c422c
10 changed files with 95 additions and 69 deletions
|
@ -81,10 +81,10 @@ class AlteraQuartusToolchain:
|
||||||
fragment = fragment.get_fragment()
|
fragment = fragment.get_fragment()
|
||||||
platform.finalize(fragment)
|
platform.finalize(fragment)
|
||||||
|
|
||||||
v_src, vns = platform.get_verilog(fragment)
|
v_output = platform.get_verilog(fragment)
|
||||||
named_sc, named_pc = platform.resolve_signals(vns)
|
named_sc, named_pc = platform.resolve_signals(v_output.ns)
|
||||||
v_file = build_name + ".v"
|
v_file = build_name + ".v"
|
||||||
tools.write_to_file(v_file, v_src)
|
v_output.write(v_file)
|
||||||
sources = platform.sources | {(v_file, "verilog")}
|
sources = platform.sources | {(v_file, "verilog")}
|
||||||
_build_files(platform.device, sources, platform.verilog_include_paths, named_sc, named_pc, build_name)
|
_build_files(platform.device, sources, platform.verilog_include_paths, named_sc, named_pc, build_name)
|
||||||
if run:
|
if run:
|
||||||
|
@ -92,7 +92,7 @@ class AlteraQuartusToolchain:
|
||||||
|
|
||||||
os.chdir("..")
|
os.chdir("..")
|
||||||
|
|
||||||
return vns
|
return v_output.ns
|
||||||
|
|
||||||
def add_period_constraint(self, platform, clk, period):
|
def add_period_constraint(self, platform, clk, period):
|
||||||
# TODO: handle differential clk
|
# TODO: handle differential clk
|
||||||
|
|
|
@ -265,20 +265,11 @@ class GenericPlatform:
|
||||||
named_pc.append(template.format(**name_dict))
|
named_pc.append(template.format(**name_dict))
|
||||||
return named_sc, named_pc
|
return named_sc, named_pc
|
||||||
|
|
||||||
def _get_source(self, fragment, gen_fn):
|
|
||||||
if not isinstance(fragment, _Fragment):
|
|
||||||
fragment = fragment.get_fragment()
|
|
||||||
# generate source
|
|
||||||
src, vns = gen_fn(fragment)
|
|
||||||
return src, vns
|
|
||||||
|
|
||||||
def get_verilog(self, fragment, **kwargs):
|
def get_verilog(self, fragment, **kwargs):
|
||||||
return self._get_source(fragment, lambda f: verilog.convert(f, self.constraint_manager.get_io_signals(),
|
return verilog.convert(fragment, self.constraint_manager.get_io_signals(), create_clock_domains=False, **kwargs)
|
||||||
return_ns=True, create_clock_domains=False, **kwargs))
|
|
||||||
|
|
||||||
def get_edif(self, fragment, cell_library, vendor, device, **kwargs):
|
def get_edif(self, fragment, cell_library, vendor, device, **kwargs):
|
||||||
return self._get_source(fragment, lambda f: edif.convert(f, self.constraint_manager.get_io_signals(),
|
return edif.convert(fragment, self.constraint_manager.get_io_signals(), cell_library, vendor, device, **kwargs)
|
||||||
cell_library, vendor, device, return_ns=True, **kwargs))
|
|
||||||
|
|
||||||
def build(self, fragment):
|
def build(self, fragment):
|
||||||
raise NotImplementedError("GenericPlatform.build must be overloaded")
|
raise NotImplementedError("GenericPlatform.build must be overloaded")
|
||||||
|
|
|
@ -75,10 +75,10 @@ class LatticeDiamondToolchain:
|
||||||
fragment = fragment.get_fragment()
|
fragment = fragment.get_fragment()
|
||||||
platform.finalize(fragment)
|
platform.finalize(fragment)
|
||||||
|
|
||||||
v_src, vns = platform.get_verilog(fragment)
|
v_output = platform.get_verilog(fragment)
|
||||||
named_sc, named_pc = platform.resolve_signals(vns)
|
named_sc, named_pc = platform.resolve_signals(v_output.ns)
|
||||||
v_file = build_name + ".v"
|
v_file = build_name + ".v"
|
||||||
tools.write_to_file(v_file, v_src)
|
v_output.write(v_file)
|
||||||
sources = platform.sources + [(v_file, "verilog")]
|
sources = platform.sources + [(v_file, "verilog")]
|
||||||
_build_files(platform.device, sources, platform.verilog_include_paths, build_name)
|
_build_files(platform.device, sources, platform.verilog_include_paths, build_name)
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ class LatticeDiamondToolchain:
|
||||||
|
|
||||||
os.chdir("..")
|
os.chdir("..")
|
||||||
|
|
||||||
return vns
|
return v_output.ns
|
||||||
|
|
||||||
def add_period_constraint(self, platform, clk, period):
|
def add_period_constraint(self, platform, clk, period):
|
||||||
# TODO: handle differential clk
|
# TODO: handle differential clk
|
||||||
|
|
|
@ -127,10 +127,9 @@ class SimVerilatorToolchain:
|
||||||
fragment = fragment.get_fragment()
|
fragment = fragment.get_fragment()
|
||||||
platform.finalize(fragment)
|
platform.finalize(fragment)
|
||||||
|
|
||||||
v_src, vns = platform.get_verilog(fragment)
|
v_output = platform.get_verilog(fragment)
|
||||||
named_sc, named_pc = platform.resolve_signals(vns)
|
named_sc, named_pc = platform.resolve_signals(v_output.ns)
|
||||||
v_file = "dut.v"
|
v_output.write("dut.v")
|
||||||
tools.write_to_file(v_file, v_src)
|
|
||||||
|
|
||||||
include_paths = []
|
include_paths = []
|
||||||
for source in platform.sources:
|
for source in platform.sources:
|
||||||
|
@ -138,11 +137,11 @@ class SimVerilatorToolchain:
|
||||||
if path not in include_paths:
|
if path not in include_paths:
|
||||||
include_paths.append(path)
|
include_paths.append(path)
|
||||||
include_paths += platform.verilog_include_paths
|
include_paths += platform.verilog_include_paths
|
||||||
_build_sim(platform, vns, build_name, include_paths, sim_path, serial, verbose)
|
_build_sim(platform, v_output.ns, build_name, include_paths, sim_path, serial, verbose)
|
||||||
|
|
||||||
if run:
|
if run:
|
||||||
_run_sim(build_name)
|
_run_sim(build_name)
|
||||||
|
|
||||||
os.chdir("..")
|
os.chdir("..")
|
||||||
|
|
||||||
return vns
|
return v_output.ns
|
||||||
|
|
|
@ -145,10 +145,11 @@ class XilinxISEToolchain:
|
||||||
vns = None
|
vns = None
|
||||||
|
|
||||||
if mode == "xst" or mode == "yosys":
|
if mode == "xst" or mode == "yosys":
|
||||||
v_src, vns = platform.get_verilog(fragment)
|
v_output = platform.get_verilog(fragment)
|
||||||
|
vns = v_output.ns
|
||||||
named_sc, named_pc = platform.resolve_signals(vns)
|
named_sc, named_pc = platform.resolve_signals(vns)
|
||||||
v_file = build_name + ".v"
|
v_file = build_name + ".v"
|
||||||
tools.write_to_file(v_file, v_src)
|
v_output.write(v_file)
|
||||||
sources = platform.sources | {(v_file, "verilog")}
|
sources = platform.sources | {(v_file, "verilog")}
|
||||||
if mode == "xst":
|
if mode == "xst":
|
||||||
_build_xst_files(platform.device, sources, platform.verilog_include_paths, build_name, self.xst_opt)
|
_build_xst_files(platform.device, sources, platform.verilog_include_paths, build_name, self.xst_opt)
|
||||||
|
@ -163,10 +164,11 @@ class XilinxISEToolchain:
|
||||||
synthesize(fragment, platform.constraint_manager.get_io_signals())
|
synthesize(fragment, platform.constraint_manager.get_io_signals())
|
||||||
|
|
||||||
if mode == "edif" or mode == "mist":
|
if mode == "edif" or mode == "mist":
|
||||||
e_src, vns = platform.get_edif(fragment)
|
e_output = platform.get_edif(fragment)
|
||||||
|
vns = e_output.ns
|
||||||
named_sc, named_pc = platform.resolve_signals(vns)
|
named_sc, named_pc = platform.resolve_signals(vns)
|
||||||
e_file = build_name + ".edif"
|
e_file = build_name + ".edif"
|
||||||
tools.write_to_file(e_file, e_src)
|
e_output.write(e_file)
|
||||||
isemode = "edif"
|
isemode = "edif"
|
||||||
|
|
||||||
tools.write_to_file(build_name + ".ucf", _build_ucf(named_sc, named_pc))
|
tools.write_to_file(build_name + ".ucf", _build_ucf(named_sc, named_pc))
|
||||||
|
|
|
@ -111,10 +111,10 @@ class XilinxVivadoToolchain:
|
||||||
if not isinstance(fragment, _Fragment):
|
if not isinstance(fragment, _Fragment):
|
||||||
fragment = fragment.get_fragment()
|
fragment = fragment.get_fragment()
|
||||||
platform.finalize(fragment)
|
platform.finalize(fragment)
|
||||||
v_src, vns = platform.get_verilog(fragment)
|
v_output = platform.get_verilog(fragment)
|
||||||
named_sc, named_pc = platform.resolve_signals(vns)
|
named_sc, named_pc = platform.resolve_signals(v_output.ns)
|
||||||
v_file = build_name + ".v"
|
v_file = build_name + ".v"
|
||||||
tools.write_to_file(v_file, v_src)
|
v_output.write(v_file)
|
||||||
sources = platform.sources | {(v_file, "verilog")}
|
sources = platform.sources | {(v_file, "verilog")}
|
||||||
self._build_batch(platform, sources, build_name)
|
self._build_batch(platform, sources, build_name)
|
||||||
tools.write_to_file(build_name + ".xdc", _build_xdc(named_sc, named_pc))
|
tools.write_to_file(build_name + ".xdc", _build_xdc(named_sc, named_pc))
|
||||||
|
@ -123,7 +123,7 @@ class XilinxVivadoToolchain:
|
||||||
|
|
||||||
os.chdir("..")
|
os.chdir("..")
|
||||||
|
|
||||||
return vns
|
return v_output.ns
|
||||||
|
|
||||||
def add_period_constraint(self, platform, clk, period):
|
def add_period_constraint(self, platform, clk, period):
|
||||||
platform.add_platform_command("""create_clock -name {clk} -period """ + \
|
platform.add_platform_command("""create_clock -name {clk} -period """ + \
|
||||||
|
|
35
migen/fhdl/conv_output.py
Normal file
35
migen/fhdl/conv_output.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
from operator import itemgetter
|
||||||
|
|
||||||
|
|
||||||
|
class ConvOutput:
|
||||||
|
def __init__(self):
|
||||||
|
self.main_source = ""
|
||||||
|
self.data_files = dict()
|
||||||
|
|
||||||
|
def set_main_source(self, src):
|
||||||
|
self.main_source = src
|
||||||
|
|
||||||
|
def add_data_file(self, filename_base, content):
|
||||||
|
filename = filename_base
|
||||||
|
i = 1
|
||||||
|
while filename in self.data_files:
|
||||||
|
parts = filename_base.split(".", maxsplit=1)
|
||||||
|
parts[0] += "_" + str(i)
|
||||||
|
filename = ".".join(parts)
|
||||||
|
i += 1
|
||||||
|
self.data_files[filename] = content
|
||||||
|
return filename
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
r = self.main_source + "\n"
|
||||||
|
for filename, content in sorted(self.data_files.items(),
|
||||||
|
key=itemgetter(0)):
|
||||||
|
r += filename + ":\n" + content
|
||||||
|
return r
|
||||||
|
|
||||||
|
def write(self, main_filename):
|
||||||
|
with open(main_filename, "w") as f:
|
||||||
|
f.write(self.main_source)
|
||||||
|
for filename, content in self.data_files.items():
|
||||||
|
with open(filename, "w") as f:
|
||||||
|
f.write(content)
|
|
@ -1,10 +1,12 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
from migen.fhdl.std import *
|
from migen.fhdl.std import *
|
||||||
from migen.fhdl.namer import build_namespace
|
from migen.fhdl.namer import build_namespace
|
||||||
from migen.fhdl.tools import list_special_ios
|
from migen.fhdl.tools import list_special_ios
|
||||||
from migen.fhdl.structure import _Fragment
|
from migen.fhdl.structure import _Fragment
|
||||||
|
from migen.fhdl.conv_output import ConvOutput
|
||||||
|
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
_Port = namedtuple("_Port", "name direction")
|
_Port = namedtuple("_Port", "name direction")
|
||||||
_Cell = namedtuple("_Cell", "name ports")
|
_Cell = namedtuple("_Cell", "name ports")
|
||||||
|
@ -125,7 +127,7 @@ def _generate_cells(f):
|
||||||
else:
|
else:
|
||||||
cell_dict[special.of] = port_list
|
cell_dict[special.of] = port_list
|
||||||
else:
|
else:
|
||||||
raise ValueError("Edif conversion can only handle synthesized fragments")
|
raise ValueError("EDIF conversion can only handle synthesized fragments")
|
||||||
return [_Cell(k, v) for k, v in cell_dict.items()]
|
return [_Cell(k, v) for k, v in cell_dict.items()]
|
||||||
|
|
||||||
def _generate_instances(f,ns):
|
def _generate_instances(f,ns):
|
||||||
|
@ -146,7 +148,7 @@ def _generate_instances(f,ns):
|
||||||
raise NotImplementedError("Unsupported instance item")
|
raise NotImplementedError("Unsupported instance item")
|
||||||
instances.append(_Instance(name=ns.get_name(special), cell=special.of, properties=props))
|
instances.append(_Instance(name=ns.get_name(special), cell=special.of, properties=props))
|
||||||
else:
|
else:
|
||||||
raise ValueError("Edif conversion can only handle synthesized fragments")
|
raise ValueError("EDIF conversion can only handle synthesized fragments")
|
||||||
return instances
|
return instances
|
||||||
|
|
||||||
def _generate_ios(f, ios, ns):
|
def _generate_ios(f, ios, ns):
|
||||||
|
@ -174,7 +176,7 @@ def _generate_connections(f, ios, ns):
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("Unsupported instance item")
|
raise NotImplementedError("Unsupported instance item")
|
||||||
else:
|
else:
|
||||||
raise ValueError("Edif conversion can only handle synthesized fragments")
|
raise ValueError("EDIF conversion can only handle synthesized fragments")
|
||||||
for s in ios:
|
for s in ios:
|
||||||
io = ns.get_name(s)
|
io = ns.get_name(s)
|
||||||
if io not in r:
|
if io not in r:
|
||||||
|
@ -182,11 +184,11 @@ def _generate_connections(f, ios, ns):
|
||||||
r[io].append(_NetBranch(portname=io, instancename=""))
|
r[io].append(_NetBranch(portname=io, instancename=""))
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def convert(f, ios, cell_library, vendor, device, name="top", return_ns=False):
|
def convert(f, ios, cell_library, vendor, device, name="top"):
|
||||||
if not isinstance(f, _Fragment):
|
if not isinstance(f, _Fragment):
|
||||||
f = f.get_fragment()
|
f = f.get_fragment()
|
||||||
if f.comb != [] or f.sync != {}:
|
if f.comb != [] or f.sync != {}:
|
||||||
raise ValueError("Edif conversion can only handle synthesized fragments")
|
raise ValueError("EDIF conversion can only handle synthesized fragments")
|
||||||
if ios is None:
|
if ios is None:
|
||||||
ios = set()
|
ios = set()
|
||||||
cells = _generate_cells(f)
|
cells = _generate_cells(f)
|
||||||
|
@ -194,8 +196,9 @@ def convert(f, ios, cell_library, vendor, device, name="top", return_ns=False):
|
||||||
instances = _generate_instances(f, ns)
|
instances = _generate_instances(f, ns)
|
||||||
inouts = _generate_ios(f, ios, ns)
|
inouts = _generate_ios(f, ios, ns)
|
||||||
connections = _generate_connections(f, ios, ns)
|
connections = _generate_connections(f, ios, ns)
|
||||||
r = _write_edif(cells, inouts, instances, connections, cell_library, name, device, vendor)
|
src = _write_edif(cells, inouts, instances, connections, cell_library, name, device, vendor)
|
||||||
if return_ns:
|
|
||||||
return r, ns
|
r = ConvOutput()
|
||||||
else:
|
r.set_main_source(src)
|
||||||
return r
|
r.ns = ns
|
||||||
|
return r
|
||||||
|
|
|
@ -48,7 +48,7 @@ class Tristate(Special):
|
||||||
yield self, attr, target_context
|
yield self, attr, target_context
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def emit_verilog(tristate, ns):
|
def emit_verilog(tristate, ns, add_data_file):
|
||||||
def pe(e):
|
def pe(e):
|
||||||
return verilog_printexpr(ns, e)[0]
|
return verilog_printexpr(ns, e)[0]
|
||||||
w, s = value_bits_sign(tristate.target)
|
w, s = value_bits_sign(tristate.target)
|
||||||
|
@ -123,7 +123,7 @@ class Instance(Special):
|
||||||
yield item, "expr", SPECIAL_INOUT
|
yield item, "expr", SPECIAL_INOUT
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def emit_verilog(instance, ns):
|
def emit_verilog(instance, ns, add_data_file):
|
||||||
r = instance.of + " "
|
r = instance.of + " "
|
||||||
parameters = list(filter(lambda i: isinstance(i, Instance.Parameter), instance.items))
|
parameters = list(filter(lambda i: isinstance(i, Instance.Parameter), instance.items))
|
||||||
if parameters:
|
if parameters:
|
||||||
|
@ -198,7 +198,7 @@ class _MemoryPort(Special):
|
||||||
yield self, attr, target_context
|
yield self, attr, target_context
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def emit_verilog(port, ns):
|
def emit_verilog(port, ns, add_data_file):
|
||||||
return "" # done by parent Memory object
|
return "" # done by parent Memory object
|
||||||
|
|
||||||
class Memory(Special):
|
class Memory(Special):
|
||||||
|
@ -237,7 +237,7 @@ class Memory(Special):
|
||||||
return mp
|
return mp
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def emit_verilog(memory, ns):
|
def emit_verilog(memory, ns, add_data_file):
|
||||||
r = ""
|
r = ""
|
||||||
def gn(e):
|
def gn(e):
|
||||||
if isinstance(e, Memory):
|
if isinstance(e, Memory):
|
||||||
|
@ -307,14 +307,10 @@ class Memory(Special):
|
||||||
r += "\n"
|
r += "\n"
|
||||||
|
|
||||||
if memory.init is not None:
|
if memory.init is not None:
|
||||||
memory_filename = gn(memory) + ".init"
|
content = ""
|
||||||
|
|
||||||
# XXX move I/O to mibuild?
|
|
||||||
# (Implies mem init won't work with simple Migen examples?)
|
|
||||||
f = open(memory_filename, "w")
|
|
||||||
for d in memory.init:
|
for d in memory.init:
|
||||||
f.write("{:x}\n".format(d))
|
content += "{:x}\n".format(d)
|
||||||
f.close()
|
memory_filename = add_data_file(gn(memory) + ".init", content)
|
||||||
|
|
||||||
r += "initial begin\n"
|
r += "initial begin\n"
|
||||||
r += "$readmemh(\"" + memory_filename + "\", " + gn(memory) + ");\n"
|
r += "$readmemh(\"" + memory_filename + "\", " + gn(memory) + ");\n"
|
||||||
|
@ -330,7 +326,7 @@ class SynthesisDirective(Special):
|
||||||
self.signals = signals
|
self.signals = signals
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def emit_verilog(directive, ns):
|
def emit_verilog(directive, ns, add_data_file):
|
||||||
name_dict = dict((k, ns.get_name(sig)) for k, sig in directive.signals.items())
|
name_dict = dict((k, ns.get_name(sig)) for k, sig in directive.signals.items())
|
||||||
formatted = directive.template.format(**name_dict)
|
formatted = directive.template.format(**name_dict)
|
||||||
return "// synthesis " + formatted + "\n"
|
return "// synthesis " + formatted + "\n"
|
||||||
|
|
|
@ -6,6 +6,7 @@ from migen.fhdl.structure import _Operator, _Slice, _Assign, _Fragment
|
||||||
from migen.fhdl.tools import *
|
from migen.fhdl.tools import *
|
||||||
from migen.fhdl.bitcontainer import bits_for, flen
|
from migen.fhdl.bitcontainer import bits_for, flen
|
||||||
from migen.fhdl.namer import Namespace, build_namespace
|
from migen.fhdl.namer import Namespace, build_namespace
|
||||||
|
from migen.fhdl.conv_output import ConvOutput
|
||||||
|
|
||||||
def _printsig(ns, s):
|
def _printsig(ns, s):
|
||||||
if s.signed:
|
if s.signed:
|
||||||
|
@ -257,20 +258,20 @@ def _lower_specials(overrides, specials):
|
||||||
f.specials -= lowered_specials2
|
f.specials -= lowered_specials2
|
||||||
return f, lowered_specials
|
return f, lowered_specials
|
||||||
|
|
||||||
def _printspecials(overrides, specials, ns):
|
def _printspecials(overrides, specials, ns, add_data_file):
|
||||||
r = ""
|
r = ""
|
||||||
for special in sorted(specials, key=lambda x: x.huid):
|
for special in sorted(specials, key=lambda x: x.huid):
|
||||||
pr = _call_special_classmethod(overrides, special, "emit_verilog", ns)
|
pr = _call_special_classmethod(overrides, special, "emit_verilog", ns, add_data_file)
|
||||||
if pr is None:
|
if pr is None:
|
||||||
raise NotImplementedError("Special " + str(special) + " failed to implement emit_verilog")
|
raise NotImplementedError("Special " + str(special) + " failed to implement emit_verilog")
|
||||||
r += pr
|
r += pr
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def convert(f, ios=None, name="top",
|
def convert(f, ios=None, name="top",
|
||||||
return_ns=False,
|
|
||||||
special_overrides=dict(),
|
special_overrides=dict(),
|
||||||
create_clock_domains=True,
|
create_clock_domains=True,
|
||||||
display_run=False):
|
display_run=False):
|
||||||
|
r = ConvOutput()
|
||||||
if not isinstance(f, _Fragment):
|
if not isinstance(f, _Fragment):
|
||||||
f = f.get_fragment()
|
f = f.get_fragment()
|
||||||
if ios is None:
|
if ios is None:
|
||||||
|
@ -296,15 +297,14 @@ def convert(f, ios=None, name="top",
|
||||||
ns = build_namespace(list_signals(f) \
|
ns = build_namespace(list_signals(f) \
|
||||||
| list_special_ios(f, True, True, True) \
|
| list_special_ios(f, True, True, True) \
|
||||||
| ios)
|
| ios)
|
||||||
|
r.ns = ns
|
||||||
|
|
||||||
r = "/* Machine-generated using Migen */\n"
|
src = "/* Machine-generated using Migen */\n"
|
||||||
r += _printheader(f, ios, name, ns)
|
src += _printheader(f, ios, name, ns)
|
||||||
r += _printcomb(f, ns, display_run)
|
src += _printcomb(f, ns, display_run)
|
||||||
r += _printsync(f, ns)
|
src += _printsync(f, ns)
|
||||||
r += _printspecials(special_overrides, f.specials - lowered_specials, ns)
|
src += _printspecials(special_overrides, f.specials - lowered_specials, ns, r.add_data_file)
|
||||||
r += "endmodule\n"
|
src += "endmodule\n"
|
||||||
|
r.set_main_source(src)
|
||||||
|
|
||||||
if return_ns:
|
return r
|
||||||
return r, ns
|
|
||||||
else:
|
|
||||||
return r
|
|
||||||
|
|
Loading…
Reference in a new issue