Merge branch 'master' of github.com:YosysHQ/yosys

This commit is contained in:
Eddie Hung 2019-11-22 15:13:18 -08:00
commit 2a54fa41c4
312 changed files with 44937 additions and 25169 deletions

View File

@ -43,6 +43,16 @@ Yosys 0.9 .. Yosys 0.9-dev
- Added "-match-init" option to "dff2dffs" pass
- Added "techmap_autopurge" support to techmap
- Added "add -mod <modname[s]>"
- Added +/mul2dsp.v for decomposing wide multipliers to custom-sized ones
- Added "ice40_dsp" for Lattice iCE40 DSP packing
- Added "xilinx_dsp" for Xilinx DSP packing
- "synth_xilinx" to now infer DSP blocks (-nodsp to disable)
- "synth_ecp5" to now infer DSP blocks (-nodsp to disable, experimental)
- "synth_ice40 -dsp" to infer DSP blocks
- Added latch support to synth_xilinx
- Added "check -mapped"
- Added checking of SystemVerilog always block types (always_comb,
always_latch and always_ff)
Yosys 0.8 .. Yosys 0.9
----------------------

View File

@ -202,6 +202,52 @@ of how to use the Yosys API:
manual/PRESENTATION_Prog/my_cmd.cc
Script Passes
-------------
The ScriptPass base class can be used to implement passes that just call other passes,
like a script. Examples for such passes are:
techlibs/common/prep.cc
techlibs/common/synth.cc
In some cases it is easier to implement such a pass as regular pass, for example when
ScriptPass doesn't provide the type of flow control desired. (But many of the
script passes in Yosys that don't use ScriptPass simply predate the ScriptPass base
class.) Examples for such passes are:
passes/opt/opt.cc
passes/proc/proc.cc
Whether they use the ScriptPass base-class or not, a pass should always either
call other passes without doing any non-trivial work itself, or should implement
a non-trivial algorithm but not call any other passes. The reason for this is that
this helps containing complexity in individual passes and simplifies debugging the
entire system.
Exceptions to this rule should be rare and limited to cases where calling other
passes is optional and only happens when requested by the user (such as for
example `techmap -autoproc`), or where it is about commands that are "top-level
commands" in their own right, not components to be used in regular synthesis
flows (such as the `bugpoint` command).
A pass that would "naturally" call other passes and also do some work itself
should be re-written in one of two ways:
1) It could be re-written as script pass with the parts that are not calls
to other passes factored out into individual new passes. Usually in those
cases the new sub passes share the same prefix as the top-level script pass.
2) It could be re-written so that it already expects the design in a certain
state, expecting the calling script to set up this state before calling the
pass in questions.
Many back-ends are examples for the 2nd approach. For example, `write_aiger`
does not convert the design into AIG representation, but expects the design
to be already in this form, and prints an `Unsupported cell type` error
message otherwise.
Notes on the existing codebase
------------------------------

View File

@ -115,7 +115,7 @@ LDFLAGS += -rdynamic
LDLIBS += -lrt
endif
YOSYS_VER := 0.9+431
YOSYS_VER := 0.9+932
GIT_REV := $(shell cd $(YOSYS_SRC) && git rev-parse --short HEAD 2> /dev/null || echo UNKNOWN)
OBJS = kernel/version_$(GIT_REV).o
@ -128,7 +128,7 @@ bumpversion:
# is just a symlink to your actual ABC working directory, as 'make mrproper'
# will remove the 'abc' directory and you do not want to accidentally
# delete your work on ABC..
ABCREV = 5776ad0
ABCREV = 623b5e8
ABCPULL = 1
ABCURL ?= https://github.com/berkeley-abc/abc
ABCMKARGS = CC="$(CXX)" CXX="$(CXX)" ABC_USE_LIBSTDCXX=1
@ -147,9 +147,9 @@ $(info $(subst $$--$$,$(newline),$(shell sed 's,^,[Makefile.conf] ,; s,$$,$$--$$
include Makefile.conf
endif
PYTHON_EXECUTABLE := $(shell if python3 -c ""; then echo "python3"; else echo "python"; fi)
ifeq ($(ENABLE_PYOSYS),1)
PYTHON_VERSION_TESTCODE := "import sys;t='{v[0]}.{v[1]}'.format(v=list(sys.version_info[:2]));print(t)"
PYTHON_EXECUTABLE := $(shell if python3 -c ""; then echo "python3"; else echo "python"; fi)
PYTHON_VERSION := $(shell $(PYTHON_EXECUTABLE) -c ""$(PYTHON_VERSION_TESTCODE)"")
PYTHON_MAJOR_VERSION := $(shell echo $(PYTHON_VERSION) | cut -f1 -d.)
PYTHON_PREFIX := $(shell $(PYTHON_EXECUTABLE)-config --prefix)
@ -528,6 +528,7 @@ $(eval $(call add_include_file,kernel/satgen.h))
$(eval $(call add_include_file,libs/ezsat/ezsat.h))
$(eval $(call add_include_file,libs/ezsat/ezminisat.h))
$(eval $(call add_include_file,libs/sha1/sha1.h))
$(eval $(call add_include_file,libs/json11/json11.hpp))
$(eval $(call add_include_file,passes/fsm/fsmdata.h))
$(eval $(call add_include_file,frontends/ast/ast.h))
$(eval $(call add_include_file,backends/ilang/ilang_backend.h))
@ -545,6 +546,8 @@ OBJS += libs/sha1/sha1.o
ifneq ($(SMALL),1)
OBJS += libs/json11/json11.o
OBJS += libs/subcircuit/subcircuit.o
OBJS += libs/ezsat/ezsat.o
@ -705,11 +708,18 @@ test: $(TARGETS) $(EXTRA_TARGETS)
+cd tests/various && bash run-test.sh
+cd tests/sat && bash run-test.sh
+cd tests/svinterfaces && bash run-test.sh $(SEEDOPT)
+cd tests/svtypes && bash run-test.sh $(SEEDOPT)
+cd tests/proc && bash run-test.sh
+cd tests/opt && bash run-test.sh
+cd tests/aiger && bash run-test.sh $(ABCOPT)
+cd tests/arch && bash run-test.sh
+cd tests/ice40 && bash run-test.sh $(SEEDOPT)
+cd tests/arch/ice40 && bash run-test.sh $(SEEDOPT)
+cd tests/arch/xilinx && bash run-test.sh $(SEEDOPT)
+cd tests/arch/ecp5 && bash run-test.sh $(SEEDOPT)
+cd tests/arch/efinix && bash run-test.sh $(SEEDOPT)
+cd tests/arch/anlogic && bash run-test.sh $(SEEDOPT)
+cd tests/arch/gowin && bash run-test.sh $(SEEDOPT)
+cd tests/rpc && bash run-test.sh
@echo ""
@echo " Passed \"make test\"."
@echo ""

View File

@ -371,6 +371,11 @@ Verilog Attributes and non-standard features
for example, to specify the clk-to-Q delay of a flip-flop for consideration
during techmapping.
- The frontend sets attributes ``always_comb``, ``always_latch`` and
``always_ff`` on processes derived from SystemVerilog style always blocks
according to the type of the always. These are checked for correctness in
``proc_dlatch``.
- In addition to the ``(* ... *)`` attribute syntax, Yosys supports
the non-standard ``{* ... *}`` attribute syntax to set default attributes
for everything that comes after the ``{* ... *}`` statement. (Reset
@ -510,6 +515,8 @@ from SystemVerilog:
into a design with ``read_verilog``, all its packages are available to
SystemVerilog files being read into the same design afterwards.
- typedefs are supported (including inside packages)
- SystemVerilog interfaces (SVIs) are supported. Modports for specifying whether
ports are inputs or outputs are supported.

View File

@ -91,6 +91,9 @@ struct AigerWriter
} else
if (alias_map.count(bit)) {
a = bit2aig(alias_map.at(bit));
} else
if (initstate_bits.count(bit)) {
a = initstate_ff;
}
if (bit == State::Sx || bit == State::Sz)
@ -777,7 +780,7 @@ struct AigerBackend : public Backend {
}
break;
}
extra_args(f, filename, args, argidx);
extra_args(f, filename, args, argidx, !ascii_mode);
Module *top_module = design->top_module();

View File

@ -203,7 +203,7 @@ struct XAigerWriter
// box ordering, but not individual AIG cells
dict<SigBit, pool<IdString>> bit_drivers, bit_users;
TopoSort<IdString, RTLIL::sort_by_id_str> toposort;
bool abc_box_seen = false;
bool abc9_box_seen = false;
for (auto cell : module->selected_cells()) {
if (cell->type == "$_NOT_")
@ -242,8 +242,8 @@ struct XAigerWriter
log_assert(!holes_mode);
RTLIL::Module* inst_module = module->design->module(cell->type);
if (inst_module && inst_module->attributes.count("\\abc_box_id")) {
abc_box_seen = true;
if (inst_module && inst_module->attributes.count("\\abc9_box_id")) {
abc9_box_seen = true;
if (!holes_mode) {
toposort.node(cell->name);
@ -291,10 +291,10 @@ struct XAigerWriter
if (is_output) {
int arrival = 0;
if (port_wire) {
auto it = port_wire->attributes.find("\\abc_arrival");
auto it = port_wire->attributes.find("\\abc9_arrival");
if (it != port_wire->attributes.end()) {
if (it->second.flags != 0)
log_error("Attribute 'abc_arrival' on port '%s' of module '%s' is not an integer.\n", log_id(port_wire), log_id(cell->type));
log_error("Attribute 'abc9_arrival' on port '%s' of module '%s' is not an integer.\n", log_id(port_wire), log_id(cell->type));
arrival = it->second.as_int();
}
}
@ -318,7 +318,7 @@ struct XAigerWriter
//log_warning("Unsupported cell type: %s (%s)\n", log_id(cell->type), log_id(cell));
}
if (abc_box_seen) {
if (abc9_box_seen) {
for (auto &it : bit_users)
if (bit_drivers.count(it.first))
for (auto driver_cell : bit_drivers.at(it.first))
@ -347,9 +347,11 @@ struct XAigerWriter
log_assert(cell);
RTLIL::Module* box_module = module->design->module(cell->type);
if (!box_module || !box_module->attributes.count("\\abc_box_id"))
if (!box_module || !box_module->attributes.count("\\abc9_box_id"))
continue;
bool blackbox = box_module->get_blackbox_attribute(true /* ignore_wb */);
// Fully pad all unused input connections of this box cell with S0
// Fully pad all undriven output connections of this box cell with anonymous wires
// NB: Assume box_module->ports are sorted alphabetically
@ -394,7 +396,10 @@ struct XAigerWriter
rhs = it->second;
}
else {
rhs = module->addWire(NEW_ID, GetSize(w));
Wire *wire = module->addWire(NEW_ID, GetSize(w));
if (blackbox)
wire->set_bool_attribute(ID(abc9_padding));
rhs = wire;
cell->setPort(port_name, rhs);
}
@ -405,15 +410,10 @@ struct XAigerWriter
if (O != b)
alias_map[O] = b;
undriven_bits.erase(O);
auto jt = input_bits.find(b);
if (jt != input_bits.end()) {
log_assert(keep_bits.count(O));
input_bits.erase(b);
}
}
}
}
box_list.emplace_back(cell);
}
@ -429,7 +429,7 @@ struct XAigerWriter
// inherit existing inout's drivers
if ((wire->port_input && wire->port_output && !undriven_bits.count(bit))
|| keep_bits.count(bit)) {
RTLIL::IdString wire_name = wire->name.str() + "$inout.out";
RTLIL::IdString wire_name = stringf("$%s$inout.out", wire->name.c_str());
RTLIL::Wire *new_wire = module->wire(wire_name);
if (!new_wire)
new_wire = module->addWire(wire_name, GetSize(wire));
@ -666,7 +666,7 @@ struct XAigerWriter
write_h_buffer(box_inputs);
write_h_buffer(box_outputs);
write_h_buffer(box_module->attributes.at("\\abc_box_id").as_int());
write_h_buffer(box_module->attributes.at("\\abc9_box_id").as_int());
write_h_buffer(box_count++);
}
@ -856,7 +856,7 @@ struct XAigerBackend : public Backend {
}
break;
}
extra_args(f, filename, args, argidx);
extra_args(f, filename, args, argidx, !ascii_mode);
Module *top_module = design->top_module();

View File

@ -569,7 +569,7 @@ struct BtorWorker
int nid_init_val = -1;
if (!initval.is_fully_undef())
nid_init_val = get_sig_nid(initval);
nid_init_val = get_sig_nid(initval, -1, false, true);
int sid = get_bv_sid(GetSize(sig_q));
int nid = next_nid++;
@ -681,7 +681,7 @@ struct BtorWorker
{
if (verbose)
btorf("; initval = %s\n", log_signal(firstword));
nid_init_val = get_sig_nid(firstword);
nid_init_val = get_sig_nid(firstword, -1, false, true);
}
else
{
@ -693,8 +693,8 @@ struct BtorWorker
if (thisword.is_fully_undef())
continue;
Const thisaddr(i, abits);
int nid_thisword = get_sig_nid(thisword);
int nid_thisaddr = get_sig_nid(thisaddr);
int nid_thisword = get_sig_nid(thisword, -1, false, true);
int nid_thisaddr = get_sig_nid(thisaddr, -1, false, true);
int last_nid_init_val = nid_init_val;
nid_init_val = next_nid++;
if (verbose)
@ -792,7 +792,7 @@ struct BtorWorker
cell_recursion_guard.erase(cell);
}
int get_sig_nid(SigSpec sig, int to_width = -1, bool is_signed = false)
int get_sig_nid(SigSpec sig, int to_width = -1, bool is_signed = false, bool is_init = false)
{
int nid = -1;
sigmap.apply(sig);
@ -823,6 +823,9 @@ struct BtorWorker
int sid = get_bv_sid(GetSize(sig));
int nid_input = next_nid++;
if (is_init)
btorf("%d state %d\n", nid_input, sid);
else
btorf("%d input %d\n", nid_input, sid);
int nid_masked_input;
@ -897,9 +900,12 @@ struct BtorWorker
int sid = get_bv_sid(GetSize(s));
int nid = next_nid++;
btorf("%d input %d %s\n", nid, sid);
btorf("%d input %d\n", nid, sid);
nid_width[nid] = GetSize(s);
for (int j = 0; j < GetSize(s); j++)
nidbits.push_back(make_pair(nid, j));
i += GetSize(s)-1;
continue;
}
@ -1064,7 +1070,12 @@ struct BtorWorker
bad_properties.push_back(nid_en_and_not_a);
} else {
int nid = next_nid++;
btorf("%d bad %d\n", nid, nid_en_and_not_a);
string infostr = log_id(cell);
if (infostr[0] == '$' && cell->attributes.count("\\src")) {
infostr = cell->attributes.at("\\src").decode_string().c_str();
std::replace(infostr.begin(), infostr.end(), ' ', '_');
}
btorf("%d bad %d %s\n", nid, nid_en_and_not_a, infostr.c_str());
}
btorf_pop(log_id(cell));

View File

@ -266,7 +266,7 @@ struct ProtobufBackend : public Backend {
}
break;
}
extra_args(f, filename, args, argidx);
extra_args(f, filename, args, argidx, !text_mode);
log_header(design, "Executing Protobuf backend.\n");
@ -338,7 +338,7 @@ struct ProtobufPass : public Pass {
if (!filename.empty()) {
rewrite_filename(filename);
std::ofstream *ff = new std::ofstream;
ff->open(filename.c_str(), std::ofstream::trunc);
ff->open(filename.c_str(), text_mode ? std::ofstream::trunc : (std::ofstream::trunc | std::ofstream::binary));
if (ff->fail()) {
delete ff;
log_error("Can't open file `%s' for writing: %s\n", filename.c_str(), strerror(errno));

View File

@ -1256,7 +1256,7 @@ def smt_check_sat():
return smt.check_sat()
if tempind:
retstatus = False
retstatus = "FAILED"
skip_counter = step_size
for step in range(num_steps, -1, -1):
if smt.forall:
@ -1303,7 +1303,7 @@ if tempind:
else:
print_msg("Temporal induction successful.")
retstatus = True
retstatus = "PASSED"
break
elif covermode:
@ -1321,7 +1321,7 @@ elif covermode:
smt.write("(define-fun covers_0 ((state |%s_s|)) (_ BitVec %d) %s)" % (topmod, len(cover_desc), cover_expr))
step = 0
retstatus = False
retstatus = "FAILED"
found_failed_assert = False
assert step_size == 1
@ -1365,7 +1365,7 @@ elif covermode:
if smt_check_sat() == "unsat":
print("%s Cannot appended steps without violating assumptions!" % smt.timestamp())
found_failed_assert = True
retstatus = False
retstatus = "FAILED"
break
reached_covers = smt.bv2bin(smt.get("(covers_%d s%d)" % (coveridx, step)))
@ -1400,7 +1400,7 @@ elif covermode:
break
if "1" not in cover_mask:
retstatus = True
retstatus = "PASSED"
break
step += 1
@ -1412,7 +1412,7 @@ elif covermode:
else: # not tempind, covermode
step = 0
retstatus = True
retstatus = "PASSED"
while step < num_steps:
smt_state(step)
smt_assert_consequent("(|%s_u| s%d)" % (topmod, step))
@ -1459,8 +1459,8 @@ else: # not tempind, covermode
print_msg("Checking assumptions in steps %d to %d.." % (step, last_check_step))
if smt_check_sat() == "unsat":
print("%s Warmup failed!" % smt.timestamp())
retstatus = False
print("%s Assumptions are unsatisfiable!" % smt.timestamp())
retstatus = "PREUNSAT"
break
if not final_only:
@ -1487,13 +1487,13 @@ else: # not tempind, covermode
print_msg("Re-solving with appended steps..")
if smt_check_sat() == "unsat":
print("%s Cannot appended steps without violating assumptions!" % smt.timestamp())
retstatus = False
retstatus = "FAILED"
break
print_anyconsts(step)
for i in range(step, last_check_step+1):
print_failed_asserts(i)
write_trace(0, last_check_step+1+append_steps, '%')
retstatus = False
retstatus = "FAILED"
break
smt_pop()
@ -1519,7 +1519,7 @@ else: # not tempind, covermode
print_anyconsts(i)
print_failed_asserts(i, final=True)
write_trace(0, i+1, '%')
retstatus = False
retstatus = "FAILED"
break
smt_pop()
@ -1534,7 +1534,7 @@ else: # not tempind, covermode
print_msg("Solving for step %d.." % (last_check_step))
if smt_check_sat() != "sat":
print("%s No solution found!" % smt.timestamp())
retstatus = False
retstatus = "FAILED"
break
elif dumpall:
@ -1551,5 +1551,5 @@ else: # not tempind, covermode
smt.write("(exit)")
smt.wait()
print_msg("Status: %s" % ("PASSED" if retstatus else "FAILED (!)"))
sys.exit(0 if retstatus else 1)
print_msg("Status: %s" % retstatus)
sys.exit(0 if retstatus == "PASSED" else 1)

View File

@ -1032,12 +1032,17 @@ class MkVcd:
print("$var integer 32 t smt_step $end", file=self.f)
print("$var event 1 ! smt_clock $end", file=self.f)
def vcdescape(n):
if n.startswith("$") or ":" in n:
return "\\" + n
return n
scope = []
for path in sorted(self.nets):
key, width = self.nets[path]
uipath = list(path)
if "." in uipath[-1]:
if "." in uipath[-1] and not uipath[-1].startswith("$"):
uipath = uipath[0:-1] + uipath[-1].split(".")
for i in range(len(uipath)):
uipath[i] = re.sub(r"\[([^\]]*)\]", r"<\1>", uipath[i])
@ -1048,15 +1053,13 @@ class MkVcd:
while uipath[:-1] != scope:
scopename = uipath[len(scope)]
if scopename.startswith("$"):
scopename = "\\" + scopename
print("$scope module %s $end" % scopename, file=self.f)
print("$scope module %s $end" % vcdescape(scopename), file=self.f)
scope.append(uipath[len(scope)])
if path in self.clocks and self.clocks[path][1] == "event":
print("$var event 1 %s %s $end" % (key, uipath[-1]), file=self.f)
print("$var event 1 %s %s $end" % (key, vcdescape(uipath[-1])), file=self.f)
else:
print("$var wire %d %s %s $end" % (width, key, uipath[-1]), file=self.f)
print("$var wire %d %s %s $end" % (width, key, vcdescape(uipath[-1])), file=self.f)
for i in range(len(scope)):
print("$upscope $end", file=self.f)

View File

@ -33,11 +33,11 @@
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
bool verbose, norename, noattr, attr2comment, noexpr, nodec, nohex, nostr, defparam, decimal, siminit;
int auto_name_counter, auto_name_offset, auto_name_digits;
bool verbose, norename, noattr, attr2comment, noexpr, nodec, nohex, nostr, extmem, defparam, decimal, siminit;
int auto_name_counter, auto_name_offset, auto_name_digits, extmem_counter;
std::map<RTLIL::IdString, int> auto_name_map;
std::set<RTLIL::IdString> reg_wires, reg_ct;
std::string auto_prefix;
std::string auto_prefix, extmem_prefix;
RTLIL::Module *active_module;
dict<RTLIL::SigBit, RTLIL::State> active_initdata;
@ -371,13 +371,14 @@ void dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig)
}
}
void dump_attributes(std::ostream &f, std::string indent, dict<RTLIL::IdString, RTLIL::Const> &attributes, char term = '\n', bool modattr = false, bool as_comment = false)
void dump_attributes(std::ostream &f, std::string indent, dict<RTLIL::IdString, RTLIL::Const> &attributes, char term = '\n', bool modattr = false, bool regattr = false, bool as_comment = false)
{
if (noattr)
return;
if (attr2comment)
as_comment = true;
for (auto it = attributes.begin(); it != attributes.end(); ++it) {
if (it->first == "\\init" && regattr) continue;
f << stringf("%s" "%s %s", indent.c_str(), as_comment ? "/*" : "(*", id(it->first).c_str());
f << stringf(" = ");
if (modattr && (it->second == State::S0 || it->second == Const(0)))
@ -392,7 +393,7 @@ void dump_attributes(std::ostream &f, std::string indent, dict<RTLIL::IdString,
void dump_wire(std::ostream &f, std::string indent, RTLIL::Wire *wire)
{
dump_attributes(f, indent, wire->attributes);
dump_attributes(f, indent, wire->attributes, '\n', /*modattr=*/false, /*regattr=*/reg_wires.count(wire->name));
#if 0
if (wire->port_input && !wire->port_output)
f << stringf("%s" "input %s", indent.c_str(), reg_wires.count(wire->name) ? "reg " : "");
@ -1067,6 +1068,55 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
// end
f << stringf("%s" "reg [%d:%d] %s [%d:%d];\n", indent.c_str(), width-1, 0, mem_id.c_str(), size+offset-1, offset);
if (use_init)
{
if (extmem)
{
std::string extmem_filename = stringf("%s-%d.mem", extmem_prefix.c_str(), extmem_counter++);
std::string extmem_filename_esc;
for (auto c : extmem_filename)
{
if (c == '\n')
extmem_filename_esc += "\\n";
else if (c == '\t')
extmem_filename_esc += "\\t";
else if (c < 32)
extmem_filename_esc += stringf("\\%03o", c);
else if (c == '"')
extmem_filename_esc += "\\\"";
else if (c == '\\')
extmem_filename_esc += "\\\\";
else
extmem_filename_esc += c;
}
f << stringf("%s" "initial $readmemb(\"%s\", %s);\n", indent.c_str(), extmem_filename_esc.c_str(), mem_id.c_str());
std::ofstream extmem_f(extmem_filename, std::ofstream::trunc);
if (extmem_f.fail())
log_error("Can't open file `%s' for writing: %s\n", extmem_filename.c_str(), strerror(errno));
else
{
for (int i=0; i<size; i++)
{
RTLIL::Const element = cell->parameters["\\INIT"].extract(i*width, width);
for (int j=0; j<element.size(); j++)
{
switch (element[element.size()-j-1])
{
case State::S0: extmem_f << '0'; break;
case State::S1: extmem_f << '1'; break;
case State::Sx: extmem_f << 'x'; break;
case State::Sz: extmem_f << 'z'; break;
case State::Sa: extmem_f << '_'; break;
case State::Sm: log_error("Found marker state in final netlist.");
}
}
extmem_f << '\n';
}
}
}
else
{
f << stringf("%s" "initial begin\n", indent.c_str());
for (int i=0; i<size; i++)
@ -1077,6 +1127,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
}
f << stringf("%s" "end\n", indent.c_str());
}
}
// create a map : "edge clk" -> expressions within that clock domain
dict<std::string, std::vector<std::string>> clk_to_lof_body;
@ -1521,7 +1572,7 @@ void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw
bool got_default = false;
for (auto it = sw->cases.begin(); it != sw->cases.end(); ++it) {
dump_attributes(f, indent + " ", (*it)->attributes, '\n', /*modattr=*/false, /*as_comment=*/true);
dump_attributes(f, indent + " ", (*it)->attributes, '\n', /*modattr=*/false, /*regattr=*/false, /*as_comment=*/true);
if ((*it)->compare.size() == 0) {
if (got_default)
continue;
@ -1686,7 +1737,7 @@ void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module)
}
}
dump_attributes(f, indent, module->attributes, '\n', /*attr2comment=*/true);
dump_attributes(f, indent, module->attributes, '\n', /*modattr=*/true);
f << stringf("%s" "module %s(", indent.c_str(), id(module->name, false).c_str());
bool keep_running = true;
for (int port_id = 1; keep_running; port_id++) {
@ -1776,8 +1827,16 @@ struct VerilogBackend : public Backend {
log(" deactivates this feature and instead will write string constants\n");
log(" as binary numbers.\n");
log("\n");
log(" -extmem\n");
log(" instead of initializing memories using assignments to individual\n");
log(" elements, use the '$readmemh' function to read initialization data\n");
log(" from a file. This data is written to a file named by appending\n");
log(" a sequential index to the Verilog filename and replacing the extension\n");
log(" with '.mem', e.g. 'write_verilog -extmem foo.v' writes 'foo-1.mem',\n");
log(" 'foo-2.mem' and so on.\n");
log("\n");
log(" -defparam\n");
log(" Use 'defparam' statements instead of the Verilog-2001 syntax for\n");
log(" use 'defparam' statements instead of the Verilog-2001 syntax for\n");
log(" cell parameters.\n");
log("\n");
log(" -blackboxes\n");
@ -1811,6 +1870,7 @@ struct VerilogBackend : public Backend {
nodec = false;
nohex = false;
nostr = false;
extmem = false;
defparam = false;
decimal = false;
siminit = false;
@ -1884,6 +1944,11 @@ struct VerilogBackend : public Backend {
nostr = true;
continue;
}
if (arg == "-extmem") {
extmem = true;
extmem_counter = 1;
continue;
}
if (arg == "-defparam") {
defparam = true;
continue;
@ -1911,6 +1976,12 @@ struct VerilogBackend : public Backend {
break;
}
extra_args(f, filename, args, argidx);
if (extmem)
{
if (filename.empty())
log_cmd_error("Option -extmem must be used with a filename.\n");
extmem_prefix = filename.substr(0, filename.rfind('.'));
}
design->sort();

View File

@ -1,41 +1,10 @@
// 50 MHz Clock
IO_LOC "clk" D11;
// LEDs
IO_LOC "leds[0]" D22;
IO_LOC "leds[1]" E22;
IO_LOC "leds[2]" G22;
IO_LOC "leds[3]" J22;
IO_LOC "leds[4]" L22;
IO_LOC "leds[5]" L19;
IO_LOC "leds[6]" L20;
IO_LOC "leds[7]" M21;
IO_LOC "leds[8]" N19;
IO_LOC "leds[9]" R19;
IO_LOC "leds[10]" T18;
IO_LOC "leds[11]" AA22;
IO_LOC "leds[12]" U18;
IO_LOC "leds[13]" V20;
IO_LOC "leds[14]" AA21;
IO_LOC "leds[15]" AB21;
// 7-Segment Display
IO_LOC "seg7dig[0]" E20;
IO_LOC "seg7dig[1]" G18;
IO_LOC "seg7dig[2]" G20;
IO_LOC "seg7dig[3]" F21;
IO_LOC "seg7dig[4]" J20;
IO_LOC "seg7dig[5]" H21;
IO_LOC "seg7dig[6]" H18;
IO_LOC "seg7dig[7]" D20;
IO_LOC "seg7sel[0]" C19;
IO_LOC "seg7sel[1]" B22;
IO_LOC "seg7sel[2]" C20;
IO_LOC "seg7sel[3]" C21;
// Switches
IO_LOC "sw[0]" AB20;
IO_LOC "sw[1]" AB19;
IO_LOC "sw[2]" AB18;
IO_LOC "sw[3]" AB17;
IO_LOC "clk" 35;
//IO_LOC "rst_n" 77;
IO_LOC "leds[0]" 79;
IO_LOC "leds[1]" 80;
IO_LOC "leds[2]" 81;
IO_LOC "leds[3]" 82;
IO_LOC "leds[4]" 83;
IO_LOC "leds[5]" 84;
IO_LOC "leds[6]" 85;
IO_LOC "leds[7]" 86;

View File

@ -1,9 +1,7 @@
module demo (
input clk,
input [3:0] sw,
output [15:0] leds,
output [7:0] seg7dig,
output [3:0] seg7sel
output unused
);
localparam PRESCALE = 20;
reg [PRESCALE+3:0] counter = 0;

16
examples/gowin/device.cfg Normal file
View File

@ -0,0 +1,16 @@
set JTAG regular_io = false
set SSPI regular_io = false
set MSPI regular_io = false
set READY regular_io = false
set DONE regular_io = false
set RECONFIG_N regular_io = false
set MODE regular_io = false
set CRC_check = true
set compress = false
set encryption = false
set security_bit_enable = true
set bsram_init_fuse_print = true
set download_speed = 250/100
set spi_flash_address = 0x00FFF000
set format = txt
set background_programming = false

8
examples/gowin/pnr.cfg Normal file
View File

@ -0,0 +1,8 @@
-sdf
-oc
-ibs
-posp
-o
-warning_all
-tt
-timing

View File

@ -1,8 +1,7 @@
#!/bin/bash
set -ex
yosys -p "synth_gowin -top demo -vout demo_syn.v" demo.v
$GOWIN_HOME/bin/gowin -d demo_syn.v -cst demo.cst -sdc demo.sdc -p GW2A55-PBGA484-6 \
-warning_all -out demo_out.v -rpt demo.rpt -tr demo_tr.html -bit demo.bit
$GOWIN_HOME/bin/gowin -d demo_syn.v -cst demo.cst -sdc demo.sdc -p GW1NR-9-QFN88-6 -pn GW1NR-LV9QN88C6/I5 -cfg device.cfg -bit -tr -ph -timing -gpa -rpt -warning_all
# post place&route simulation (icarus verilog)
if false; then

9
examples/gowin/run.tcl Normal file
View File

@ -0,0 +1,9 @@
# gw_sh run.tcl
exec yosys -p "synth_gowin -top demo -vout demo_syn.v" demo.v
add_file -cst demo.cst
add_file -sdc demo.sdc
add_file -vm demo_syn.v
add_file -cfg device.cfg
set_option -device GW1NR-9-QFN88-6
set_option -pn GW1NR-LV9QN88C6/I5
run_pnr -opt pnr.cfg

View File

@ -285,6 +285,8 @@ end_of_header:
}
else if (c == 'c') {
f.ignore(1);
if (f.peek() == '\r')
f.ignore(1);
if (f.peek() == '\n')
break;
// Else constraint (TODO)
@ -738,22 +740,22 @@ void AigerReader::post_process()
log_assert(box_module);
if (seen_boxes.insert(cell->type).second) {
auto it = box_module->attributes.find("\\abc_carry");
auto it = box_module->attributes.find("\\abc9_carry");
if (it != box_module->attributes.end()) {
RTLIL::Wire *carry_in = nullptr, *carry_out = nullptr;
auto carry_in_out = it->second.decode_string();
auto pos = carry_in_out.find(',');
if (pos == std::string::npos)
log_error("'abc_carry' attribute on module '%s' does not contain ','.\n", log_id(cell->type));
log_error("'abc9_carry' attribute on module '%s' does not contain ','.\n", log_id(cell->type));
auto carry_in_name = RTLIL::escape_id(carry_in_out.substr(0, pos));
carry_in = box_module->wire(carry_in_name);
if (!carry_in || !carry_in->port_input)
log_error("'abc_carry' on module '%s' contains '%s' which does not exist or is not an input port.\n", log_id(cell->type), carry_in_name.c_str());
log_error("'abc9_carry' on module '%s' contains '%s' which does not exist or is not an input port.\n", log_id(cell->type), carry_in_name.c_str());
auto carry_out_name = RTLIL::escape_id(carry_in_out.substr(pos+1));
carry_out = box_module->wire(carry_out_name);
if (!carry_out || !carry_out->port_output)
log_error("'abc_carry' on module '%s' contains '%s' which does not exist or is not an output port.\n", log_id(cell->type), carry_out_name.c_str());
log_error("'abc9_carry' on module '%s' contains '%s' which does not exist or is not an output port.\n", log_id(cell->type), carry_out_name.c_str());
auto &ports = box_module->ports;
for (auto jt = ports.begin(); jt != ports.end(); ) {
@ -868,7 +870,7 @@ void AigerReader::post_process()
if (!existing) {
if (escaped_s.ends_with("$inout.out")) {
wire->port_output = false;
RTLIL::Wire *in_wire = module->wire(escaped_s.substr(0, escaped_s.size()-10));
RTLIL::Wire *in_wire = module->wire(escaped_s.substr(1, escaped_s.size()-11));
log_assert(in_wire);
log_assert(in_wire->port_input && !in_wire->port_output);
in_wire->port_output = true;
@ -889,7 +891,7 @@ void AigerReader::post_process()
if (!existing) {
if (escaped_s.ends_with("$inout.out")) {
wire->port_output = false;
RTLIL::Wire *in_wire = module->wire(stringf("%s[%d]", escaped_s.substr(0, escaped_s.size()-10).c_str(), index));
RTLIL::Wire *in_wire = module->wire(stringf("%s[%d]", escaped_s.substr(1, escaped_s.size()-11).c_str(), index));
log_assert(in_wire);
log_assert(in_wire->port_input && !in_wire->port_output);
in_wire->port_output = true;
@ -1056,13 +1058,15 @@ struct AigerFrontend : public Frontend {
}
break;
}
extra_args(f, filename, args, argidx);
extra_args(f, filename, args, argidx, true);
if (module_name.empty()) {
#ifdef _WIN32
char fname[_MAX_FNAME];
_splitpath(filename.c_str(), NULL /* drive */, NULL /* dir */, fname, NULL /* ext */);
module_name = fname;
char* bn = strdup(fname);
module_name = RTLIL::escape_id(bn);
free(bn);
#else
char* bn = strdup(filename.c_str());
module_name = RTLIL::escape_id(bn);

View File

@ -164,6 +164,8 @@ std::string AST::type2str(AstNodeType type)
X(AST_MODPORT)
X(AST_MODPORTMEMBER)
X(AST_PACKAGE)
X(AST_WIRETYPE)
X(AST_TYPEDEF)
#undef X
default:
log_abort();
@ -206,6 +208,7 @@ AstNode::AstNode(AstNodeType type, AstNode *child1, AstNode *child2, AstNode *ch
was_checked = false;
range_valid = false;
range_swapped = false;
is_custom_type = false;
port_id = 0;
range_left = -1;
range_right = 0;
@ -1382,10 +1385,10 @@ void AstModule::reprocess_module(RTLIL::Design *design, dict<RTLIL::IdString, RT
// create a new parametric module (when needed) and return the name of the generated module - WITH support for interfaces
// This method is used to explode the interface when the interface is a port of the module (not instantiated inside)
RTLIL::IdString AstModule::derive(RTLIL::Design *design, dict<RTLIL::IdString, RTLIL::Const> parameters, dict<RTLIL::IdString, RTLIL::Module*> interfaces, dict<RTLIL::IdString, RTLIL::IdString> modports, bool mayfail)
RTLIL::IdString AstModule::derive(RTLIL::Design *design, dict<RTLIL::IdString, RTLIL::Const> parameters, dict<RTLIL::IdString, RTLIL::Module*> interfaces, dict<RTLIL::IdString, RTLIL::IdString> modports, bool /*mayfail*/)
{
AstNode *new_ast = NULL;
std::string modname = derive_common(design, parameters, &new_ast, mayfail);
std::string modname = derive_common(design, parameters, &new_ast);
// Since interfaces themselves may be instantiated with different parameters,
// "modname" must also take those into account, so that unique modules
@ -1398,11 +1401,17 @@ RTLIL::IdString AstModule::derive(RTLIL::Design *design, dict<RTLIL::IdString, R
has_interfaces = true;
}
std::string new_modname = modname;
if (has_interfaces)
modname += "$interfaces$" + interf_info;
new_modname += "$interfaces$" + interf_info;
if (!design->has(modname)) {
if (!design->has(new_modname)) {
if (!new_ast) {
auto mod = dynamic_cast<AstModule*>(design->module(modname));
new_ast = mod->ast->clone();
}
modname = new_modname;
new_ast->str = modname;
// Iterate over all interfaces which are ports in this module:
@ -1455,10 +1464,10 @@ RTLIL::IdString AstModule::derive(RTLIL::Design *design, dict<RTLIL::IdString, R
}
// create a new parametric module (when needed) and return the name of the generated module - without support for interfaces
RTLIL::IdString AstModule::derive(RTLIL::Design *design, dict<RTLIL::IdString, RTLIL::Const> parameters, bool mayfail)
RTLIL::IdString AstModule::derive(RTLIL::Design *design, dict<RTLIL::IdString, RTLIL::Const> parameters, bool /*mayfail*/)
{
AstNode *new_ast = NULL;
std::string modname = derive_common(design, parameters, &new_ast, mayfail);
std::string modname = derive_common(design, parameters, &new_ast);
if (!design->has(modname)) {
new_ast->str = modname;
@ -1473,31 +1482,66 @@ RTLIL::IdString AstModule::derive(RTLIL::Design *design, dict<RTLIL::IdString, R
}
// create a new parametric module (when needed) and return the name of the generated module
std::string AstModule::derive_common(RTLIL::Design *design, dict<RTLIL::IdString, RTLIL::Const> parameters, AstNode **new_ast_out, bool)
std::string AstModule::derive_common(RTLIL::Design *design, dict<RTLIL::IdString, RTLIL::Const> parameters, AstNode **new_ast_out)
{
std::string stripped_name = name.str();
if (stripped_name.compare(0, 9, "$abstract") == 0)
stripped_name = stripped_name.substr(9);
log_header(design, "Executing AST frontend in derive mode using pre-parsed AST for module `%s'.\n", stripped_name.c_str());
loadconfig();
std::string para_info;
AstNode *new_ast = ast->clone();
int para_counter = 0;
int orig_parameters_n = parameters.size();
for (auto it = new_ast->children.begin(); it != new_ast->children.end(); it++) {
AstNode *child = *it;
for (const auto child : ast->children) {
if (child->type != AST_PARAMETER)
continue;
para_counter++;
std::string para_id = child->str;
if (parameters.count(para_id) > 0) {
log("Parameter %s = %s\n", child->str.c_str(), log_signal(RTLIL::SigSpec(parameters[child->str])));
rewrite_parameter:
para_info += stringf("%s=%s", child->str.c_str(), log_signal(RTLIL::SigSpec(parameters[para_id])));
continue;
}
para_id = stringf("$%d", para_counter);
if (parameters.count(para_id) > 0) {
log("Parameter %d (%s) = %s\n", para_counter, child->str.c_str(), log_signal(RTLIL::SigSpec(parameters[para_id])));
para_info += stringf("%s=%s", child->str.c_str(), log_signal(RTLIL::SigSpec(parameters[para_id])));
continue;
}
}
std::string modname;
if (parameters.size() == 0)
modname = stripped_name;
else if (para_info.size() > 60)
modname = "$paramod$" + sha1(para_info) + stripped_name;
else
modname = "$paramod" + stripped_name + para_info;
if (design->has(modname))
return modname;
log_header(design, "Executing AST frontend in derive mode using pre-parsed AST for module `%s'.\n", stripped_name.c_str());
loadconfig();
AstNode *new_ast = ast->clone();
para_counter = 0;
for (auto child : new_ast->children) {
if (child->type != AST_PARAMETER)
continue;
para_counter++;
std::string para_id = child->str;
if (parameters.count(para_id) > 0) {
log("Parameter %s = %s\n", child->str.c_str(), log_signal(RTLIL::SigSpec(parameters[child->str])));
goto rewrite_parameter;
}
para_id = stringf("$%d", para_counter);
if (parameters.count(para_id) > 0) {
log("Parameter %d (%s) = %s\n", para_counter, child->str.c_str(), log_signal(RTLIL::SigSpec(parameters[para_id])));
goto rewrite_parameter;
}
continue;
rewrite_parameter:
delete child->children.at(0);
if ((parameters[para_id].flags & RTLIL::CONST_FLAG_REAL) != 0) {
child->children[0] = new AstNode(AST_REALVALUE);
@ -1507,13 +1551,6 @@ std::string AstModule::derive_common(RTLIL::Design *design, dict<RTLIL::IdString
else
child->children[0] = AstNode::mkconst_bits(parameters[para_id].bits, (parameters[para_id].flags & RTLIL::CONST_FLAG_SIGNED) != 0);
parameters.erase(para_id);
continue;
}
para_id = stringf("$%d", para_counter);
if (parameters.count(para_id) > 0) {
log("Parameter %d (%s) = %s\n", para_counter, child->str.c_str(), log_signal(RTLIL::SigSpec(parameters[para_id])));
goto rewrite_parameter;
}
}
for (auto param : parameters) {
@ -1526,16 +1563,6 @@ std::string AstModule::derive_common(RTLIL::Design *design, dict<RTLIL::IdString
new_ast->children.push_back(defparam);
}
std::string modname;
if (orig_parameters_n == 0)
modname = stripped_name;
else if (para_info.size() > 60)
modname = "$paramod$" + sha1(para_info) + stripped_name;
else
modname = "$paramod" + stripped_name + para_info;
(*new_ast_out) = new_ast;
return modname;
}

View File

@ -148,7 +148,10 @@ namespace AST
AST_INTERFACEPORTTYPE,
AST_MODPORT,
AST_MODPORTMEMBER,
AST_PACKAGE
AST_PACKAGE,
AST_WIRETYPE,
AST_TYPEDEF
};
// convert an node type to a string (e.g. for debug output)
@ -174,7 +177,7 @@ namespace AST
// node content - most of it is unused in most node types
std::string str;
std::vector<RTLIL::State> bits;
bool is_input, is_output, is_reg, is_logic, is_signed, is_string, is_wand, is_wor, range_valid, range_swapped, was_checked, is_unsized;
bool is_input, is_output, is_reg, is_logic, is_signed, is_string, is_wand, is_wor, range_valid, range_swapped, was_checked, is_unsized, is_custom_type;
int port_id, range_left, range_right;
uint32_t integer;
double realvalue;
@ -296,7 +299,7 @@ namespace AST
~AstModule() YS_OVERRIDE;
RTLIL::IdString derive(RTLIL::Design *design, dict<RTLIL::IdString, RTLIL::Const> parameters, bool mayfail) YS_OVERRIDE;
RTLIL::IdString derive(RTLIL::Design *design, dict<RTLIL::IdString, RTLIL::Const> parameters, dict<RTLIL::IdString, RTLIL::Module*> interfaces, dict<RTLIL::IdString, RTLIL::IdString> modports, bool mayfail) YS_OVERRIDE;
std::string derive_common(RTLIL::Design *design, dict<RTLIL::IdString, RTLIL::Const> parameters, AstNode **new_ast_out, bool mayfail);
std::string derive_common(RTLIL::Design *design, dict<RTLIL::IdString, RTLIL::Const> parameters, AstNode **new_ast_out);
void reprocess_module(RTLIL::Design *design, dict<RTLIL::IdString, RTLIL::Module *> local_interfaces) YS_OVERRIDE;
RTLIL::Module *clone() const YS_OVERRIDE;
void loadconfig() const;

View File

@ -863,6 +863,7 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint)
case AST_PACKAGE:
case AST_MODPORT:
case AST_MODPORTMEMBER:
case AST_TYPEDEF:
break;
case AST_INTERFACEPORT: {
// If a port in a module with unknown type is found, mark it with the attribute 'is_interface'

View File

@ -318,7 +318,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
}
// activate const folding if this is anything that must be evaluated statically (ranges, parameters, attributes, etc.)
if (type == AST_WIRE || type == AST_PARAMETER || type == AST_LOCALPARAM || type == AST_DEFPARAM || type == AST_PARASET || type == AST_RANGE || type == AST_PREFIX)
if (type == AST_WIRE || type == AST_PARAMETER || type == AST_LOCALPARAM || type == AST_DEFPARAM || type == AST_PARASET || type == AST_RANGE || type == AST_PREFIX || type == AST_TYPEDEF)
const_fold = true;
if (type == AST_IDENTIFIER && current_scope.count(str) > 0 && (current_scope[str]->type == AST_PARAMETER || current_scope[str]->type == AST_LOCALPARAM))
const_fold = true;
@ -336,6 +336,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
std::map<std::string, AstNode*> this_wire_scope;
for (size_t i = 0; i < children.size(); i++) {
AstNode *node = children[i];
if (node->type == AST_WIRE) {
if (node->children.size() == 1 && node->children[0]->type == AST_RANGE) {
for (auto c : node->children[0]->children) {
@ -405,14 +406,15 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
this_wire_scope[node->str] = node;
}
if (node->type == AST_PARAMETER || node->type == AST_LOCALPARAM || node->type == AST_WIRE || node->type == AST_AUTOWIRE || node->type == AST_GENVAR ||
node->type == AST_MEMORY || node->type == AST_FUNCTION || node->type == AST_TASK || node->type == AST_DPI_FUNCTION || node->type == AST_CELL) {
node->type == AST_MEMORY || node->type == AST_FUNCTION || node->type == AST_TASK || node->type == AST_DPI_FUNCTION || node->type == AST_CELL ||
node->type == AST_TYPEDEF) {
backup_scope[node->str] = current_scope[node->str];
current_scope[node->str] = node;
}
}
for (size_t i = 0; i < children.size(); i++) {
AstNode *node = children[i];
if (node->type == AST_PARAMETER || node->type == AST_LOCALPARAM || node->type == AST_WIRE || node->type == AST_AUTOWIRE || node->type == AST_MEMORY)
if (node->type == AST_PARAMETER || node->type == AST_LOCALPARAM || node->type == AST_WIRE || node->type == AST_AUTOWIRE || node->type == AST_MEMORY || node->type == AST_TYPEDEF)
while (node->simplify(true, false, false, 1, -1, false, node->type == AST_PARAMETER || node->type == AST_LOCALPARAM))
did_something = true;
}
@ -780,6 +782,99 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
delete_children();
}
// resolve typedefs
if (type == AST_TYPEDEF) {
log_assert(children.size() == 1);
log_assert(children[0]->type == AST_WIRE || children[0]->type == AST_MEMORY);
while(children[0]->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param))
did_something = true;
log_assert(!children[0]->is_custom_type);
}
// resolve types of wires
if (type == AST_WIRE || type == AST_MEMORY) {
if (is_custom_type) {
log_assert(children.size() >= 1);
log_assert(children[0]->type == AST_WIRETYPE);
if (!current_scope.count(children[0]->str))
log_file_error(filename, linenum, "Unknown identifier `%s' used as type name\n", children[0]->str.c_str());
AstNode *resolved_type = current_scope.at(children[0]->str);
if (resolved_type->type != AST_TYPEDEF)
log_file_error(filename, linenum, "`%s' does not name a type\n", children[0]->str.c_str());
log_assert(resolved_type->children.size() == 1);
AstNode *templ = resolved_type->children[0];
// Remove type reference
delete children[0];
children.erase(children.begin());
// Ensure typedef itself is fully simplified
while(templ->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {};
if (type == AST_WIRE)
type = templ->type;
is_reg = templ->is_reg;
is_logic = templ->is_logic;
is_signed = templ->is_signed;
is_string = templ->is_string;
is_custom_type = templ->is_custom_type;
range_valid = templ->range_valid;
range_swapped = templ->range_swapped;
range_left = templ->range_left;
range_right = templ->range_right;
// Insert clones children from template at beginning
for (int i = 0; i < GetSize(templ->children); i++)
children.insert(children.begin() + i, templ->children[i]->clone());
if (type == AST_MEMORY && GetSize(children) == 1) {
// Single-bit memories must have [0:0] range
AstNode *rng = new AstNode(AST_RANGE);
rng->children.push_back(AstNode::mkconst_int(0, true));
rng->children.push_back(AstNode::mkconst_int(0, true));
children.insert(children.begin(), rng);
}
did_something = true;
}
log_assert(!is_custom_type);
}
// resolve types of parameters
if (type == AST_LOCALPARAM || type == AST_PARAMETER) {
if (is_custom_type) {
log_assert(children.size() == 2);
log_assert(children[1]->type == AST_WIRETYPE);
if (!current_scope.count(children[1]->str))
log_file_error(filename, linenum, "Unknown identifier `%s' used as type name\n", children[1]->str.c_str());
AstNode *resolved_type = current_scope.at(children[1]->str);
if (resolved_type->type != AST_TYPEDEF)
log_file_error(filename, linenum, "`%s' does not name a type\n", children[1]->str.c_str());
log_assert(resolved_type->children.size() == 1);
AstNode *templ = resolved_type->children[0];
delete children[1];
children.pop_back();
// Ensure typedef itself is fully simplified
while(templ->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {};
if (templ->type == AST_MEMORY)
log_file_error(filename, linenum, "unpacked array type `%s' cannot be used for a parameter\n", children[1]->str.c_str());
is_signed = templ->is_signed;
is_string = templ->is_string;
is_custom_type = templ->is_custom_type;
range_valid = templ->range_valid;
range_swapped = templ->range_swapped;
range_left = templ->range_left;
range_right = templ->range_right;
for (auto template_child : templ->children)
children.push_back(template_child->clone());
did_something = true;
}
log_assert(!is_custom_type);
}
// resolve constant prefixes
if (type == AST_PREFIX) {
if (children[0]->type != AST_CONSTANT) {
@ -1194,7 +1289,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
if (type == AST_BLOCK && str.empty())
{
for (size_t i = 0; i < children.size(); i++)
if (children[i]->type == AST_WIRE || children[i]->type == AST_MEMORY || children[i]->type == AST_PARAMETER || children[i]->type == AST_LOCALPARAM)
if (children[i]->type == AST_WIRE || children[i]->type == AST_MEMORY || children[i]->type == AST_PARAMETER || children[i]->type == AST_LOCALPARAM || children[i]->type == AST_TYPEDEF)
log_file_error(children[i]->filename, children[i]->linenum, "Local declaration in unnamed block is an unsupported SystemVerilog feature!\n");
}
@ -1206,7 +1301,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
std::vector<AstNode*> new_children;
for (size_t i = 0; i < children.size(); i++)
if (children[i]->type == AST_WIRE || children[i]->type == AST_MEMORY || children[i]->type == AST_PARAMETER || children[i]->type == AST_LOCALPARAM) {
if (children[i]->type == AST_WIRE || children[i]->type == AST_MEMORY || children[i]->type == AST_PARAMETER || children[i]->type == AST_LOCALPARAM || children[i]->type == AST_TYPEDEF) {
children[i]->simplify(false, false, false, stage, -1, false, false);
current_ast_mod->children.push_back(children[i]);
current_scope[children[i]->str] = children[i];
@ -2906,7 +3001,7 @@ void AstNode::expand_genblock(std::string index_var, std::string prefix, std::ma
}
}
if ((type == AST_IDENTIFIER || type == AST_FCALL || type == AST_TCALL) && name_map.count(str) > 0)
if ((type == AST_IDENTIFIER || type == AST_FCALL || type == AST_TCALL || type == AST_WIRETYPE) && name_map.count(str) > 0)
str = name_map[str];
std::map<std::string, std::string> backup_name_map;
@ -2914,7 +3009,7 @@ void AstNode::expand_genblock(std::string index_var, std::string prefix, std::ma
for (size_t i = 0; i < children.size(); i++) {
AstNode *child = children[i];
if (child->type == AST_WIRE || child->type == AST_MEMORY || child->type == AST_PARAMETER || child->type == AST_LOCALPARAM ||
child->type == AST_FUNCTION || child->type == AST_TASK || child->type == AST_CELL) {
child->type == AST_FUNCTION || child->type == AST_TASK || child->type == AST_CELL || child->type == AST_TYPEDEF) {
if (backup_name_map.size() == 0)
backup_name_map = name_map;
std::string new_name = prefix[0] == '\\' ? prefix.substr(1) : prefix;
@ -2945,6 +3040,7 @@ void AstNode::expand_genblock(std::string index_var, std::string prefix, std::ma
child->expand_genblock(index_var, prefix, name_map);
}
if (backup_name_map.size() > 0)
name_map.swap(backup_name_map);
}
@ -2998,6 +3094,9 @@ void AstNode::mem2reg_as_needed_pass1(dict<AstNode*, pool<std::string>> &mem2reg
uint32_t children_flags = 0;
int lhs_children_counter = 0;
if (type == AST_TYPEDEF)
return; // don't touch content of typedefs
if (type == AST_ASSIGN || type == AST_ASSIGN_LE || type == AST_ASSIGN_EQ)
{
// mark all memories that are used in a complex expression on the left side of an assignment
@ -3155,6 +3254,9 @@ bool AstNode::mem2reg_as_needed_pass2(pool<AstNode*> &mem2reg_set, AstNode *mod,
if (type == AST_FUNCTION || type == AST_TASK)
return false;
if (type == AST_TYPEDEF)
return false;
if (type == AST_MEMINIT && id2ast && mem2reg_set.count(id2ast))
{
log_assert(children[0]->type == AST_CONSTANT);

View File

@ -174,6 +174,12 @@ void parse_blif(RTLIL::Design *design, std::istream &f, IdString dff_name, bool
if (module == nullptr)
goto error;
if (!strcmp(cmd, ".blackbox"))
{
module->attributes["\\blackbox"] = RTLIL::Const(1);
continue;
}
if (!strcmp(cmd, ".end"))
{
for (auto &wp : wideports_cache)
@ -280,7 +286,7 @@ void parse_blif(RTLIL::Design *design, std::istream &f, IdString dff_name, bool
goto error_with_reason;
}
module->rename(lastcell, p);
module->rename(lastcell, RTLIL::escape_id(p));
continue;
}

View File

@ -0,0 +1,2 @@
OBJS += frontends/rpc/rpc_frontend.o

View File

@ -0,0 +1,595 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2019 whitequark <whitequark@whitequark.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
// The reason the -path mode of connect_rpc uses byte-oriented and not message-oriented sockets, even though
// it is a message-oriented interface, is that the system can place various limits on the message size, which
// are not always transparent or easy to change. Given that generated HDL code get be extremely large, it is
// unwise to rely on those limits being large enough, and using byte-oriented sockets is guaranteed to work.
#ifndef _WIN32
#include <unistd.h>
#include <spawn.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/un.h>
extern char **environ;
#endif
#include "libs/json11/json11.hpp"
#include "libs/sha1/sha1.h"
#include "kernel/yosys.h"
YOSYS_NAMESPACE_BEGIN
#if defined(_WIN32)
static std::wstring str2wstr(const std::string &in) {
if(in == "") return L"";
std::wstring out;
out.resize(MultiByteToWideChar(/*CodePage=*/CP_UTF8, /*dwFlags=*/0, /*lpMultiByteStr=*/&in[0], /*cbMultiByte=*/(int)in.length(), /*lpWideCharStr=*/NULL, /*cchWideChar=*/0));
int written = MultiByteToWideChar(/*CodePage=*/CP_UTF8, /*dwFlags=*/0, /*lpMultiByteStr=*/&in[0], /*cbMultiByte=*/(int)in.length(), /*lpWideCharStr=*/&out[0], /*cchWideChar=*/(int)out.length());
log_assert(written == (int)out.length());
return out;
}
static std::string wstr2str(const std::wstring &in) {
if(in == L"") return "";
std::string out;
out.resize(WideCharToMultiByte(/*CodePage=*/CP_UTF8, /*dwFlags=*/0, /*lpWideCharStr=*/&in[0], /*cchWideChar=*/(int)in.length(), /*lpMultiByteStr=*/NULL, /*cbMultiByte=*/0, /*lpDefaultChar=*/NULL, /*lpUsedDefaultChar=*/NULL));
int written = WideCharToMultiByte(/*CodePage=*/CP_UTF8, /*dwFlags=*/0, /*lpWideCharStr=*/&in[0], /*cchWideChar=*/(int)in.length(), /*lpMultiByteStr=*/&out[0], /*cbMultiByte=*/(int)out.length(), /*lpDefaultChar=*/NULL, /*lpUsedDefaultChar=*/NULL);
log_assert(written == (int)out.length());
return out;
}
static std::string get_last_error_str() {
DWORD last_error = GetLastError();
LPWSTR out_w;
DWORD size_w = FormatMessageW(/*dwFlags=*/FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_IGNORE_INSERTS, /*lpSource=*/NULL, /*dwMessageId=*/last_error, /*dwLanguageId=*/0, /*lpBuffer=*/(LPWSTR)&out_w, /*nSize=*/0, /*Arguments=*/NULL);
if (size_w == 0)
return std::to_string(last_error);
std::string out = wstr2str(std::wstring(out_w, size_w));
LocalFree(out_w);
return out;
}
#endif
using json11::Json;
struct RpcServer {
std::string name;
RpcServer(const std::string &name) : name(name) { }
virtual ~RpcServer() { }
virtual void write(const std::string &data) = 0;
virtual std::string read() = 0;
Json call(const Json &json_request) {
std::string request;
json_request.dump(request);
request += '\n';
log_debug("RPC frontend request: %s", request.c_str());
write(request);
std::string response = read();
log_debug("RPC frontend response: %s", response.c_str());
std::string error;
Json json_response = Json::parse(response, error);
if (json_response.is_null())
log_cmd_error("parsing JSON failed: %s\n", error.c_str());
if (json_response["error"].is_string())
log_cmd_error("RPC frontend returned an error: %s\n", json_response["error"].string_value().c_str());
return json_response;
}
std::vector<std::string> get_module_names() {
Json response = call(Json::object {
{ "method", "modules" },
});
bool is_valid = true;
std::vector<std::string> modules;
if (response["modules"].is_array()) {
for (auto &json_module : response["modules"].array_items()) {
if (json_module.is_string())
modules.push_back(json_module.string_value());
else is_valid = false;
}
} else is_valid = false;
if (!is_valid)
log_cmd_error("RPC frontend returned malformed response: %s\n", response.dump().c_str());
return modules;
}
std::pair<std::string, std::string> derive_module(const std::string &module, const dict<RTLIL::IdString, RTLIL::Const> &parameters) {
Json::object json_parameters;
for (auto &param : parameters) {
std::string type, value;
if (param.second.flags & RTLIL::CONST_FLAG_REAL) {
type = "real";
value = param.second.decode_string();
} else if (param.second.flags & RTLIL::CONST_FLAG_STRING) {
type = "string";
value = param.second.decode_string();
} else if ((param.second.flags & ~RTLIL::CONST_FLAG_SIGNED) == RTLIL::CONST_FLAG_NONE) {
type = (param.second.flags & RTLIL::CONST_FLAG_SIGNED) ? "signed" : "unsigned";
value = param.second.as_string();
} else
log_cmd_error("Unserializable constant flags 0x%x\n", param.second.flags);
json_parameters[param.first.str()] = Json::object {
{ "type", type },
{ "value", value },
};
}
Json response = call(Json::object {
{ "method", "derive" },
{ "module", module },
{ "parameters", json_parameters },
});
bool is_valid = true;
std::string frontend, source;
if (response["frontend"].is_string())
frontend = response["frontend"].string_value();
else is_valid = false;
if (response["source"].is_string())
source = response["source"].string_value();
else is_valid = false;
if (!is_valid)
log_cmd_error("RPC frontend returned malformed response: %s\n", response.dump().c_str());
return std::make_pair(frontend, source);
}
};
struct RpcModule : RTLIL::Module {
std::shared_ptr<RpcServer> server;
RTLIL::IdString derive(RTLIL::Design *design, dict<RTLIL::IdString, RTLIL::Const> parameters, bool /*mayfail*/) YS_OVERRIDE {
std::string stripped_name = name.str();
if (stripped_name.compare(0, 9, "$abstract") == 0)
stripped_name = stripped_name.substr(9);
log_assert(stripped_name[0] == '\\');
log_header(design, "Executing RPC frontend `%s' for module `%s'.\n", server->name.c_str(), stripped_name.c_str());
std::string parameter_info;
for (auto &param : parameters) {
log("Parameter %s = %s\n", param.first.c_str(), log_signal(RTLIL::SigSpec(param.second)));
parameter_info += stringf("%s=%s", param.first.c_str(), log_signal(RTLIL::SigSpec(param.second)));
}
std::string derived_name;
if (parameters.empty())
derived_name = stripped_name;
else if (parameter_info.size() > 60)
derived_name = "$paramod$" + sha1(parameter_info) + stripped_name;
else
derived_name = "$paramod" + stripped_name + parameter_info;
if (design->has(derived_name)) {
log("Found cached RTLIL representation for module `%s'.\n", derived_name.c_str());
} else {
std::string command, input;
std::tie(command, input) = server->derive_module(stripped_name.substr(1), parameters);
std::istringstream input_stream(input);
RTLIL::Design *derived_design = new RTLIL::Design;
Frontend::frontend_call(derived_design, &input_stream, "<rpc>" + derived_name.substr(8), command);
derived_design->check();
dict<std::string, std::string> name_mangling;
bool found_derived_top = false;
for (auto module : derived_design->modules()) {
std::string original_name = module->name.str();
if (original_name == stripped_name) {
found_derived_top = true;
name_mangling[original_name] = derived_name;
} else {
name_mangling[original_name] = derived_name + module->name.str();
}
}
if (!found_derived_top)
log_cmd_error("RPC frontend did not return requested module `%s`!\n", stripped_name.c_str());
for (auto module : derived_design->modules())
for (auto cell : module->cells())
if (name_mangling.count(cell->type.str()))
cell->type = name_mangling[cell->type.str()];
for (auto module : derived_design->modules_) {
std::string mangled_name = name_mangling[module.first.str()];
log("Importing `%s' as `%s'.\n", log_id(module.first), log_id(mangled_name));
module.second->name = mangled_name;
module.second->design = design;
module.second->attributes.erase("\\top");
design->modules_[mangled_name] = module.second;
derived_design->modules_.erase(module.first);
}
delete derived_design;
}
return derived_name;
}
RTLIL::Module *clone() const YS_OVERRIDE {
RpcModule *new_mod = new RpcModule;
new_mod->server = server;
cloneInto(new_mod);
return new_mod;
}
};
#if defined(_WIN32)
#if defined(_MSC_VER)
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
#endif
struct HandleRpcServer : RpcServer {
HANDLE hsend, hrecv;
HandleRpcServer(const std::string &name, HANDLE hsend, HANDLE hrecv)
: RpcServer(name), hsend(hsend), hrecv(hrecv) { }
void write(const std::string &data) YS_OVERRIDE {
log_assert(data.length() >= 1 && data.find('\n') == data.length() - 1);
ssize_t offset = 0;
do {
DWORD data_written;
if (!WriteFile(hsend, &data[offset], data.length() - offset, &data_written, /*lpOverlapped=*/NULL))
log_cmd_error("WriteFile failed: %s\n", get_last_error_str().c_str());
offset += data_written;
} while(offset < (ssize_t)data.length());
}
std::string read() YS_OVERRIDE {
std::string data;
ssize_t offset = 0;
while (data.length() == 0 || data[data.length() - 1] != '\n') {
data.resize(data.length() + 1024);
DWORD data_read;
if (!ReadFile(hrecv, &data[offset], data.length() - offset, &data_read, /*lpOverlapped=*/NULL))
log_cmd_error("ReadFile failed: %s\n", get_last_error_str().c_str());
offset += data_read;
data.resize(offset);
size_t term_pos = data.find('\n', offset);
if (term_pos != data.length() - 1 && term_pos != std::string::npos)
log_cmd_error("read failed: more than one response\n");
}
return data;
}
~HandleRpcServer() {
CloseHandle(hsend);
if (hrecv != hsend)
CloseHandle(hrecv);
}
};
#else
struct FdRpcServer : RpcServer {
int fdsend, fdrecv;
pid_t pid;
FdRpcServer(const std::string &name, int fdsend, int fdrecv, pid_t pid = -1)
: RpcServer(name), fdsend(fdsend), fdrecv(fdrecv), pid(pid) { }
void check_pid() {
if (pid == -1) return;
// If we're communicating with a process, check that it's still running, or we may get killed with SIGPIPE.
pid_t wait_result = ::waitpid(pid, NULL, WNOHANG);
if (wait_result == -1)
log_cmd_error("waitpid failed: %s\n", strerror(errno));
if (wait_result == pid)
log_cmd_error("RPC frontend terminated unexpectedly\n");
}
void write(const std::string &data) YS_OVERRIDE {
log_assert(data.length() >= 1 && data.find('\n') == data.length() - 1);
ssize_t offset = 0;
do {
check_pid();
ssize_t result = ::write(fdsend, &data[offset], data.length() - offset);
if (result == -1)
log_cmd_error("write failed: %s\n", strerror(errno));
offset += result;
} while(offset < (ssize_t)data.length());
}
std::string read() YS_OVERRIDE {
std::string data;
ssize_t offset = 0;
while (data.length() == 0 || data[data.length() - 1] != '\n') {
data.resize(data.length() + 1024);
check_pid();
ssize_t result = ::read(fdrecv, &data[offset], data.length() - offset);
if (result == -1)
log_cmd_error("read failed: %s\n", strerror(errno));
offset += result;
data.resize(offset);
size_t term_pos = data.find('\n', offset);
if (term_pos != data.length() - 1 && term_pos != std::string::npos)
log_cmd_error("read failed: more than one response\n");
}
return data;
}
~FdRpcServer() {
close(fdsend);
if (fdrecv != fdsend)
close(fdrecv);
}
};
#endif
// RpcFrontend does not inherit from Frontend since it does not read files.
struct RpcFrontend : public Pass {
RpcFrontend() : Pass("connect_rpc", "connect to RPC frontend") { }
void help() YS_OVERRIDE
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" connect_rpc -exec <command> [args...]\n");
log(" connect_rpc -path <path>\n");
log("\n");
log("Load modules using an out-of-process frontend.\n");
log("\n");
log(" -exec <command> [args...]\n");
log(" run <command> with arguments [args...]. send requests on stdin, read\n");
log(" responses from stdout.\n");
log("\n");
log(" -path <path>\n");
log(" connect to Unix domain socket at <path>. (Unix)\n");
log(" connect to bidirectional byte-type named pipe at <path>. (Windows)\n");
log("\n");
log("A simple JSON-based, newline-delimited protocol is used for communicating with\n");
log("the frontend. Yosys requests data from the frontend by sending exactly 1 line\n");
log("of JSON. Frontend responds with data or error message by replying with exactly\n");
log("1 line of JSON as well.\n");
log("\n");
log(" -> {\"method\": \"modules\"}\n");
log(" <- {\"modules\": [\"<module-name>\", ...]}\n");
log(" <- {\"error\": \"<error-message>\"}\n");
log(" request for the list of modules that can be derived by this frontend.\n");
log(" the 'hierarchy' command will call back into this frontend if a cell\n");
log(" with type <module-name> is instantiated in the design.\n");
log("\n");
log(" -> {\"method\": \"derive\", \"module\": \"<module-name\">, \"parameters\": {\n");
log(" \"<param-name>\": {\"type\": \"[unsigned|signed|string|real]\",\n");
log(" \"value\": \"<param-value>\"}, ...}}\n");
log(" <- {\"frontend\": \"[ilang|verilog|...]\",\"source\": \"<source>\"}}\n");
log(" <- {\"error\": \"<error-message>\"}\n");
log(" request for the module <module-name> to be derived for a specific set of\n");
log(" parameters. <param-name> starts with \\ for named parameters, and with $\n");
log(" for unnamed parameters, which are numbered starting at 1.<param-value>\n");
log(" for integer parameters is always specified as a binary string of unlimited\n");
log(" precision. the <source> returned by the frontend is hygienically parsed\n");
log(" by a built-in Yosys <frontend>, allowing the RPC frontend to return any\n");
log(" convenient representation of the module. the derived module is cached,\n");
log(" so the response should be the same whenever the same set of parameters\n");
log(" is provided.\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
{
log_header(design, "Connecting to RPC frontend.\n");
std::vector<std::string> command;
std::string path;
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) {
std::string arg = args[argidx];
if (arg == "-exec" && argidx+1 < args.size()) {
command.insert(command.begin(), args.begin() + argidx + 1, args.end());
continue;
}
if (arg == "-path" && argidx+1 < args.size()) {
path = args[argidx+1];
continue;
}
break;
}
extra_args(args, argidx, design);
if ((!command.empty()) + (!path.empty()) != 1)
log_cmd_error("Exactly one of -exec, -unix must be specified.\n");
std::shared_ptr<RpcServer> server;
if (!command.empty()) {
std::string command_line;
bool first = true;
for (auto &arg : command) {
if (!first) command_line += ' ';
command_line += arg;
first = false;
}
#ifdef _WIN32
std::wstring command_w = str2wstr(command[0]);
std::wstring command_path_w;
std::wstring command_line_w = str2wstr(command_line);
DWORD command_path_len_w;
SECURITY_ATTRIBUTES pipe_attr = {};
HANDLE send_r = NULL, send_w = NULL, recv_r = NULL, recv_w = NULL;
STARTUPINFOW startup_info = {};
PROCESS_INFORMATION proc_info = {};
command_path_len_w = SearchPathW(/*lpPath=*/NULL, /*lpFileName=*/command_w.c_str(), /*lpExtension=*/L".exe", /*nBufferLength=*/0, /*lpBuffer=*/NULL, /*lpFilePart=*/NULL);
if (command_path_len_w == 0) {
log_error("SearchPathW failed: %s\n", get_last_error_str().c_str());
goto cleanup_exec;
}
command_path_w.resize(command_path_len_w - 1);
command_path_len_w = SearchPathW(/*lpPath=*/NULL, /*lpFileName=*/command_w.c_str(), /*lpExtension=*/L".exe", /*nBufferLength=*/command_path_len_w, /*lpBuffer=*/&command_path_w[0], /*lpFilePart=*/NULL);
log_assert(command_path_len_w == command_path_w.length());
pipe_attr.nLength = sizeof(pipe_attr);
pipe_attr.bInheritHandle = TRUE;
pipe_attr.lpSecurityDescriptor = NULL;
if (!CreatePipe(&send_r, &send_w, &pipe_attr, /*nSize=*/0)) {
log_error("CreatePipe failed: %s\n", get_last_error_str().c_str());
goto cleanup_exec;
}
if (!SetHandleInformation(send_w, HANDLE_FLAG_INHERIT, 0)) {
log_error("SetHandleInformation failed: %s\n", get_last_error_str().c_str());
goto cleanup_exec;
}
if (!CreatePipe(&recv_r, &recv_w, &pipe_attr, /*nSize=*/0)) {
log_error("CreatePipe failed: %s\n", get_last_error_str().c_str());
goto cleanup_exec;
}
if (!SetHandleInformation(recv_r, HANDLE_FLAG_INHERIT, 0)) {
log_error("SetHandleInformation failed: %s\n", get_last_error_str().c_str());
goto cleanup_exec;
}
startup_info.cb = sizeof(startup_info);
startup_info.hStdInput = send_r;
startup_info.hStdOutput = recv_w;
startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
startup_info.dwFlags |= STARTF_USESTDHANDLES;
if (!CreateProcessW(/*lpApplicationName=*/command_path_w.c_str(), /*lpCommandLine=*/&command_line_w[0], /*lpProcessAttributes=*/NULL, /*lpThreadAttributes=*/NULL, /*bInheritHandles=*/TRUE, /*dwCreationFlags=*/0, /*lpEnvironment=*/NULL, /*lpCurrentDirectory=*/NULL, &startup_info, &proc_info)) {
log_error("CreateProcessW failed: %s\n", get_last_error_str().c_str());
goto cleanup_exec;
}
CloseHandle(proc_info.hProcess);
CloseHandle(proc_info.hThread);
server = std::make_shared<HandleRpcServer>(path, send_w, recv_r);
send_w = NULL;
recv_r = NULL;
cleanup_exec:
if (send_r != NULL) CloseHandle(send_r);
if (send_w != NULL) CloseHandle(send_w);
if (recv_r != NULL) CloseHandle(recv_r);
if (recv_w != NULL) CloseHandle(recv_w);
#else
std::vector<char *> argv;
int send[2] = {-1,-1}, recv[2] = {-1,-1};
posix_spawn_file_actions_t file_actions, *file_actions_p = NULL;
pid_t pid;
for (auto &arg : command)
argv.push_back(&arg[0]);
argv.push_back(nullptr);
if (pipe(send) != 0) {
log_error("pipe failed: %s\n", strerror(errno));
goto cleanup_exec;
}
if (pipe(recv) != 0) {
log_error("pipe failed: %s\n", strerror(errno));
goto cleanup_exec;
}
if (posix_spawn_file_actions_init(&file_actions) != 0) {
log_error("posix_spawn_file_actions_init failed: %s\n", strerror(errno));
goto cleanup_exec;
}
file_actions_p = &file_actions;
if (posix_spawn_file_actions_adddup2(file_actions_p, send[0], STDIN_FILENO) != 0) {
log_error("posix_spawn_file_actions_adddup2 failed: %s\n", strerror(errno));
goto cleanup_exec;
}
if (posix_spawn_file_actions_addclose(file_actions_p, send[1]) != 0) {
log_error("posix_spawn_file_actions_addclose failed: %s\n", strerror(errno));
goto cleanup_exec;
}
if (posix_spawn_file_actions_adddup2(file_actions_p, recv[1], STDOUT_FILENO) != 0) {
log_error("posix_spawn_file_actions_adddup2 failed: %s\n", strerror(errno));
goto cleanup_exec;
}
if (posix_spawn_file_actions_addclose(file_actions_p, recv[0]) != 0) {
log_error("posix_spawn_file_actions_addclose failed: %s\n", strerror(errno));
goto cleanup_exec;
}
if (posix_spawnp(&pid, argv[0], file_actions_p, /*attrp=*/NULL, argv.data(), environ) != 0) {
log_error("posix_spawnp failed: %s\n", strerror(errno));
goto cleanup_exec;
}
server = std::make_shared<FdRpcServer>(command_line, send[1], recv[0], pid);
send[1] = -1;
recv[0] = -1;
cleanup_exec:
if (send[0] != -1) close(send[0]);
if (send[1] != -1) close(send[1]);
if (recv[0] != -1) close(recv[0]);
if (recv[1] != -1) close(recv[1]);
if (file_actions_p != NULL)
posix_spawn_file_actions_destroy(file_actions_p);
#endif
} else if (!path.empty()) {
#ifdef _WIN32
std::wstring path_w = str2wstr(path);
HANDLE h;
h = CreateFileW(path_w.c_str(), GENERIC_READ|GENERIC_WRITE, /*dwShareMode=*/0, /*lpSecurityAttributes=*/NULL, /*dwCreationDisposition=*/OPEN_EXISTING, /*dwFlagsAndAttributes=*/0, /*hTemplateFile=*/NULL);
if (h == INVALID_HANDLE_VALUE) {
log_error("CreateFileW failed: %s\n", get_last_error_str().c_str());
goto cleanup_path;
}
server = std::make_shared<HandleRpcServer>(path, h, h);
cleanup_path:
;
#else
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path) - 1);
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
log_error("socket failed: %s\n", strerror(errno));
goto cleanup_path;
}
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
log_error("connect failed: %s\n", strerror(errno));
goto cleanup_path;
}
server = std::make_shared<FdRpcServer>(path, fd, fd);
fd = -1;
cleanup_path:
if (fd != -1) close(fd);
#endif
}
if (!server)
log_cmd_error("Failed to connect to RPC frontend.\n");
for (auto &module_name : server->get_module_names()) {
log("Linking module `%s'.\n", module_name.c_str());
RpcModule *module = new RpcModule;
module->name = "$abstract\\" + module_name;
module->server = server;
design->add(module);
}
}
} RpcFrontend;
YOSYS_NAMESPACE_END

View File

@ -130,7 +130,7 @@ RTLIL::SigBit VerificImporter::net_map_at(Net *net)
bool is_blackbox(Netlist *nl)
{
if (nl->IsBlackBox())
if (nl->IsBlackBox() || nl->IsEmptyBox())
return true;
const char *attr = nl->GetAttValue("blackbox");
@ -784,10 +784,21 @@ void VerificImporter::merge_past_ffs(pool<RTLIL::Cell*> &candidates)
merge_past_ffs_clock(it.second, it.first.first, it.first.second);
}
void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::set<Netlist*> &nl_todo)
void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::set<Netlist*> &nl_todo, bool norename)
{
std::string netlist_name = nl->GetAtt(" \\top") ? nl->CellBaseName() : nl->Owner()->Name();
std::string module_name = nl->IsOperator() ? "$verific$" + netlist_name : RTLIL::escape_id(netlist_name);
std::string module_name = netlist_name;
if (nl->IsOperator() || nl->IsPrimitive()) {
module_name = "$verific$" + module_name;
} else {
if (!norename && *nl->Name()) {
module_name += "(";
module_name += nl->Name();
module_name += ")";
}
module_name = "\\" + module_name;
}
netlist = nl;
@ -1256,7 +1267,7 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::se
if (inst->Type() == PRIM_SVA_ASSERT || inst->Type() == PRIM_SVA_IMMEDIATE_ASSERT)
sva_asserts.insert(inst);
if (inst->Type() == PRIM_SVA_ASSUME || inst->Type() == PRIM_SVA_IMMEDIATE_ASSUME)
if (inst->Type() == PRIM_SVA_ASSUME || inst->Type() == PRIM_SVA_IMMEDIATE_ASSUME || inst->Type() == PRIM_SVA_RESTRICT)
sva_assumes.insert(inst);
if (inst->Type() == PRIM_SVA_COVER || inst->Type() == PRIM_SVA_IMMEDIATE_COVER)
@ -1396,8 +1407,20 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::se
import_verific_cells:
nl_todo.insert(inst->View());
RTLIL::Cell *cell = module->addCell(inst_name, inst->IsOperator() ?
std::string("$verific$") + inst->View()->Owner()->Name() : RTLIL::escape_id(inst->View()->Owner()->Name()));
std::string inst_type = inst->View()->Owner()->Name();
if (inst->View()->IsOperator() || inst->View()->IsPrimitive()) {
inst_type = "$verific$" + inst_type;
} else {
if (*inst->View()->Name()) {
inst_type += "(";
inst_type += inst->View()->Name();
inst_type += ")";
}
inst_type = "\\" + inst_type;
}
RTLIL::Cell *cell = module->addCell(inst_name, inst_type);
if (inst->IsPrimitive() && mode_keep)
cell->attributes["\\keep"] = 1;
@ -1876,7 +1899,7 @@ void verific_import(Design *design, const std::map<std::string,std::string> &par
Netlist *nl = *nl_todo.begin();
if (nl_done.count(nl) == 0) {
VerificImporter importer(false, false, false, false, false, false, false);
importer.import_netlist(design, nl, nl_todo);
importer.import_netlist(design, nl, nl_todo, nl->Owner()->Name() == top);
}
nl_todo.erase(nl);
nl_done.insert(nl);
@ -1939,12 +1962,18 @@ struct VerificPass : public Pass {
log("Load the specified VHDL files into Verific.\n");
log("\n");
log("\n");
log(" verific -work <libname> {-sv|-vhdl|...} <hdl-file>\n");
log(" verific [-work <libname>] {-sv|-vhdl|...} <hdl-file>\n");
log("\n");
log("Load the specified Verilog/SystemVerilog/VHDL file into the specified library.\n");
log("(default library when -work is not present: \"work\")\n");
log("\n");
log("\n");
log(" verific [-L <libname>] {-sv|-vhdl|...} <hdl-file>\n");
log("\n");
log("Look up external definitions in the specified library.\n");
log("(-L may be used more than once)\n");
log("\n");
log("\n");
log(" verific -vlog-incdir <directory>..\n");
log("\n");
log("Add Verilog include directories.\n");
@ -2158,12 +2187,17 @@ struct VerificPass : public Pass {
goto check_error;
}
veri_file::RemoveAllLOptions();
for (; argidx < GetSize(args); argidx++)
{
if (args[argidx] == "-work" && argidx+1 < GetSize(args)) {
work = args[++argidx];
continue;
}
if (args[argidx] == "-L" && argidx+1 < GetSize(args)) {
veri_file::AddLOption(args[++argidx].c_str());
continue;
}
break;
}
@ -2339,6 +2373,8 @@ struct VerificPass : public Pass {
if (argidx > GetSize(args) && args[argidx].compare(0, 1, "-") == 0)
cmd_error(args, argidx, "unknown option");
std::set<std::string> top_mod_names;
if (mode_all)
{
log("Running hier_tree::ElaborateAll().\n");
@ -2367,6 +2403,7 @@ struct VerificPass : public Pass {
for (; argidx < GetSize(args); argidx++)
{
const char *name = args[argidx].c_str();
top_mod_names.insert(name);
VeriLibrary* veri_lib = veri_file::GetLibrary(work.c_str(), 1);
if (veri_lib) {
@ -2432,7 +2469,7 @@ struct VerificPass : public Pass {
if (nl_done.count(nl) == 0) {
VerificImporter importer(mode_gates, mode_keep, mode_nosva,
mode_names, mode_verific, mode_autocover, mode_fullinit);
importer.import_netlist(design, nl, nl_todo);
importer.import_netlist(design, nl, nl_todo, top_mod_names.count(nl->Owner()->Name()));
}
nl_todo.erase(nl);
nl_done.insert(nl);

View File

@ -93,7 +93,7 @@ struct VerificImporter
void merge_past_ffs_clock(pool<RTLIL::Cell*> &candidates, SigBit clock, bool clock_pol);
void merge_past_ffs(pool<RTLIL::Cell*> &candidates);
void import_netlist(RTLIL::Design *design, Verific::Netlist *nl, std::set<Verific::Netlist*> &nl_todo);
void import_netlist(RTLIL::Design *design, Verific::Netlist *nl, std::set<Verific::Netlist*> &nl_todo, bool norename = false);
};
void verific_import_sva_assert(VerificImporter *importer, Verific::Instance *inst);

View File

@ -36,6 +36,8 @@
// basic_property:
// sequence
// not basic_property
// nexttime basic_property
// nexttime[N] basic_property
// sequence #-# basic_property
// sequence #=# basic_property
// basic_property or basic_property (cover only)
@ -1264,6 +1266,26 @@ struct VerificSvaImporter
return node;
}
if (inst->Type() == PRIM_SVA_NEXTTIME || inst->Type() == PRIM_SVA_S_NEXTTIME)
{
const char *sva_low_s = inst->GetAttValue("sva:low");
const char *sva_high_s = inst->GetAttValue("sva:high");
int sva_low = atoi(sva_low_s);
int sva_high = atoi(sva_high_s);
log_assert(sva_low == sva_high);
int node = start_node;
for (int i = 0; i < sva_low; i++) {
int next_node = fsm.createNode();
fsm.createEdge(node, next_node);
node = next_node;
}
return parse_sequence(fsm, node, inst->GetInput());
}
if (inst->Type() == PRIM_SVA_SEQ_CONCAT)
{
const char *sva_low_s = inst->GetAttValue("sva:low");
@ -1590,15 +1612,25 @@ struct VerificSvaImporter
Instance *consequent_inst = net_to_ast_driver(consequent_net);
if (consequent_inst && (consequent_inst->Type() == PRIM_SVA_UNTIL || consequent_inst->Type() == PRIM_SVA_S_UNTIL ||
consequent_inst->Type() == PRIM_SVA_UNTIL_WITH || consequent_inst->Type() == PRIM_SVA_S_UNTIL_WITH))
consequent_inst->Type() == PRIM_SVA_UNTIL_WITH || consequent_inst->Type() == PRIM_SVA_S_UNTIL_WITH ||
consequent_inst->Type() == PRIM_SVA_ALWAYS || consequent_inst->Type() == PRIM_SVA_S_ALWAYS))
{
bool until_with = consequent_inst->Type() == PRIM_SVA_UNTIL_WITH || consequent_inst->Type() == PRIM_SVA_S_UNTIL_WITH;
Net *until_net = consequent_inst->GetInput2();
Net *until_net = nullptr;
if (consequent_inst->Type() == PRIM_SVA_ALWAYS || consequent_inst->Type() == PRIM_SVA_S_ALWAYS)
{
consequent_net = consequent_inst->GetInput();
consequent_inst = net_to_ast_driver(consequent_net);
}
else
{
until_net = consequent_inst->GetInput2();
consequent_net = consequent_inst->GetInput1();
consequent_inst = net_to_ast_driver(consequent_net);
}
SigBit until_sig = parse_expression(until_net);
SigBit until_sig = until_net ? parse_expression(until_net) : RTLIL::S0;
SigBit not_until_sig = module->Not(NEW_ID, until_sig);
antecedent_fsm.createEdge(node, node, not_until_sig);

View File

@ -490,6 +490,7 @@ std::string frontend_verilog_preproc(std::istream &f, std::string filename, cons
}
while (newline_count-- > 0)
return_char('\n');
if (strchr("abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ$0123456789", name[0])) {
// printf("define: >>%s<< -> >>%s<<\n", name.c_str(), value.c_str());
defines_map[name] = value;
if (state == 2)
@ -497,6 +498,9 @@ std::string frontend_verilog_preproc(std::istream &f, std::string filename, cons
else
defines_with_args.erase(name);
global_defines_cache[name] = std::pair<std::string, bool>(value, state == 2);
} else {
log_file_error(filename, 0, "Invalid name for macro definition: >>%s<<.\n", name.c_str());
}
continue;
}

View File

@ -553,6 +553,12 @@ struct VerilogDefines : public Pass {
log(" -Uname[=definition]\n");
log(" undefine the preprocessor symbol 'name'\n");
log("\n");
log(" -reset\n");
log(" clear list of defined preprocessor symbols\n");
log("\n");
log(" -list\n");
log(" list currently defined preprocessor symbols\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
{
@ -588,6 +594,16 @@ struct VerilogDefines : public Pass {
design->verilog_defines.erase(name);
continue;
}
if (arg == "-reset") {
design->verilog_defines.clear();
continue;
}
if (arg == "-list") {
for (auto &it : design->verilog_defines) {
log("`define %s%s %s\n", it.first.c_str(), it.second.second ? "()" : "", it.second.first.c_str());
}
continue;
}
break;
}

View File

@ -188,9 +188,9 @@ YOSYS_NAMESPACE_END
"unique0" { SV_KEYWORD(TOK_UNIQUE); }
"priority" { SV_KEYWORD(TOK_PRIORITY); }
"always_comb" { SV_KEYWORD(TOK_ALWAYS); }
"always_ff" { SV_KEYWORD(TOK_ALWAYS); }
"always_latch" { SV_KEYWORD(TOK_ALWAYS); }
"always_comb" { SV_KEYWORD(TOK_ALWAYS_COMB); }
"always_ff" { SV_KEYWORD(TOK_ALWAYS_FF); }
"always_latch" { SV_KEYWORD(TOK_ALWAYS_LATCH); }
/* use special token for labels on assert, assume, cover, and restrict because it's insanley complex
to fix parsing of cells otherwise. (the current cell parser forces a reduce very early to update some

View File

@ -141,6 +141,7 @@ struct specify_rise_fall {
%token TOK_INTERFACE TOK_ENDINTERFACE TOK_MODPORT TOK_VAR
%token TOK_INPUT TOK_OUTPUT TOK_INOUT TOK_WIRE TOK_WAND TOK_WOR TOK_REG TOK_LOGIC
%token TOK_INTEGER TOK_SIGNED TOK_ASSIGN TOK_ALWAYS TOK_INITIAL
%token TOK_ALWAYS_FF TOK_ALWAYS_COMB TOK_ALWAYS_LATCH
%token TOK_BEGIN TOK_END TOK_IF TOK_ELSE TOK_FOR TOK_WHILE TOK_REPEAT
%token TOK_DPI_FUNCTION TOK_POSEDGE TOK_NEGEDGE TOK_OR TOK_AUTOMATIC
%token TOK_CASE TOK_CASEX TOK_CASEZ TOK_ENDCASE TOK_DEFAULT
@ -155,8 +156,8 @@ struct specify_rise_fall {
%type <ast> range range_or_multirange non_opt_range non_opt_multirange range_or_signed_int
%type <ast> wire_type expr basic_expr concat_list rvalue lvalue lvalue_concat_list
%type <string> opt_label opt_sva_label tok_prim_wrapper hierarchical_id
%type <boolean> opt_signed opt_property unique_case_attr
%type <string> opt_label opt_sva_label tok_prim_wrapper hierarchical_id hierarchical_type_id
%type <boolean> opt_signed opt_property unique_case_attr always_comb_or_latch always_or_always_ff
%type <al> attr case_attr
%type <specify_target_ptr> specify_target
@ -206,6 +207,7 @@ design:
task_func_decl design |
param_decl design |
localparam_decl design |
typedef_decl design |
package design |
interface design |
/* empty */;
@ -290,6 +292,9 @@ hierarchical_id:
$$ = $1;
};
hierarchical_type_id:
'(' hierarchical_id ')' { $$ = $2; };
module:
attr TOK_MODULE TOK_ID {
do_not_require_port_stubs = false;
@ -324,13 +329,13 @@ single_module_para:
astbuf1 = new AstNode(AST_PARAMETER);
astbuf1->children.push_back(AstNode::mkconst_int(0, true));
append_attr(astbuf1, $1);
} param_signed param_integer param_range single_param_decl |
} param_type single_param_decl |
attr TOK_LOCALPARAM {
if (astbuf1) delete astbuf1;
astbuf1 = new AstNode(AST_LOCALPARAM);
astbuf1->children.push_back(AstNode::mkconst_int(0, true));
append_attr(astbuf1, $1);
} param_signed param_integer param_range single_param_decl |
} param_type single_param_decl |
single_param_decl;
module_args_opt:
@ -426,6 +431,7 @@ package_body:
package_body package_body_stmt |;
package_body_stmt:
typedef_decl |
localparam_decl;
interface:
@ -452,7 +458,7 @@ interface_body:
interface_body interface_body_stmt |;
interface_body_stmt:
param_decl | localparam_decl | defparam_decl | wire_decl | always_stmt | assign_stmt |
param_decl | localparam_decl | typedef_decl | defparam_decl | wire_decl | always_stmt | assign_stmt |
modport_stmt;
non_opt_delay:
@ -475,8 +481,14 @@ wire_type:
};
wire_type_token_list:
wire_type_token | wire_type_token_list wire_type_token |
wire_type_token_io ;
wire_type_token |
wire_type_token_list wire_type_token |
wire_type_token_io |
hierarchical_type_id {
astbuf3->is_custom_type = true;
astbuf3->children.push_back(new AstNode(AST_WIRETYPE));
astbuf3->children.back()->str = *$1;
};
wire_type_token_io:
TOK_INPUT {
@ -591,7 +603,7 @@ module_body:
/* empty */;
module_body_stmt:
task_func_decl | specify_block |param_decl | localparam_decl | defparam_decl | specparam_declaration | wire_decl | assign_stmt | cell_stmt |
task_func_decl | specify_block | param_decl | localparam_decl | typedef_decl | defparam_decl | specparam_declaration | wire_decl | assign_stmt | cell_stmt |
always_stmt | TOK_GENERATE module_gen_body TOK_ENDGENERATE | defattr | assert_property | checker_decl | ignored_specify_block;
checker_decl:
@ -1149,12 +1161,20 @@ param_range:
}
};
param_type:
param_signed param_integer param_real param_range |
hierarchical_type_id {
astbuf1->is_custom_type = true;
astbuf1->children.push_back(new AstNode(AST_WIRETYPE));
astbuf1->children.back()->str = *$1;
};
param_decl:
attr TOK_PARAMETER {
astbuf1 = new AstNode(AST_PARAMETER);
astbuf1->children.push_back(AstNode::mkconst_int(0, true));
append_attr(astbuf1, $1);
} param_signed param_integer param_real param_range param_decl_list ';' {
} param_type param_decl_list ';' {
delete astbuf1;
};
@ -1163,7 +1183,7 @@ localparam_decl:
astbuf1 = new AstNode(AST_LOCALPARAM);
astbuf1->children.push_back(AstNode::mkconst_int(0, true));
append_attr(astbuf1, $1);
} param_signed param_integer param_real param_range param_decl_list ';' {
} param_type param_decl_list ';' {
delete astbuf1;
};
@ -1327,7 +1347,7 @@ wire_name:
if ($2 != NULL) {
if (node->is_input || node->is_output)
frontend_verilog_yyerror("input/output/inout ports cannot have unpacked dimensions.");
if (!astbuf2) {
if (!astbuf2 && !node->is_custom_type) {
AstNode *rng = new AstNode(AST_RANGE);
rng->children.push_back(AstNode::mkconst_int(0, true));
rng->children.push_back(AstNode::mkconst_int(0, true));
@ -1377,6 +1397,45 @@ assign_expr:
ast_stack.back()->children.push_back(new AstNode(AST_ASSIGN, $1, $3));
};
typedef_decl:
TOK_TYPEDEF wire_type range TOK_ID range_or_multirange ';' {
astbuf1 = $2;
astbuf2 = $3;
if (astbuf1->range_left >= 0 && astbuf1->range_right >= 0) {
if (astbuf2) {
frontend_verilog_yyerror("integer/genvar types cannot have packed dimensions.");
} else {
astbuf2 = new AstNode(AST_RANGE);
astbuf2->children.push_back(AstNode::mkconst_int(astbuf1->range_left, true));
astbuf2->children.push_back(AstNode::mkconst_int(astbuf1->range_right, true));
}
}
if (astbuf2 && astbuf2->children.size() != 2)
frontend_verilog_yyerror("wire/reg/logic packed dimension must be of the form: [<expr>:<expr>], [<expr>+:<expr>], or [<expr>-:<expr>]");
if (astbuf2)
astbuf1->children.push_back(astbuf2);
if ($5 != NULL) {
if (!astbuf2) {
AstNode *rng = new AstNode(AST_RANGE);
rng->children.push_back(AstNode::mkconst_int(0, true));
rng->children.push_back(AstNode::mkconst_int(0, true));
astbuf1->children.push_back(rng);
}
astbuf1->type = AST_MEMORY;
auto *rangeNode = $5;
if (rangeNode->type == AST_RANGE && rangeNode->children.size() == 1) {
// SV array size [n], rewrite as [n-1:0]
rangeNode->children[0] = new AstNode(AST_SUB, rangeNode->children[0], AstNode::mkconst_int(1, true));
rangeNode->children.push_back(AstNode::mkconst_int(0, false));
}
astbuf1->children.push_back(rangeNode);
}
ast_stack.back()->children.push_back(new AstNode(AST_TYPEDEF, astbuf1));
ast_stack.back()->children.back()->str = *$4;
};
cell_stmt:
attr TOK_ID {
astbuf1 = new AstNode(AST_CELL);
@ -1523,10 +1582,28 @@ cell_port:
free_attr($1);
};
always_comb_or_latch:
TOK_ALWAYS_COMB {
$$ = false;
} |
TOK_ALWAYS_LATCH {
$$ = true;
};
always_or_always_ff:
TOK_ALWAYS {
$$ = false;
} |
TOK_ALWAYS_FF {
$$ = true;
};
always_stmt:
attr TOK_ALWAYS {
attr always_or_always_ff {
AstNode *node = new AstNode(AST_ALWAYS);
append_attr(node, $1);
if ($2)
node->attributes[ID(always_ff)] = AstNode::mkconst_int(1, false);
ast_stack.back()->children.push_back(node);
ast_stack.push_back(node);
} always_cond {
@ -1537,6 +1614,22 @@ always_stmt:
ast_stack.pop_back();
ast_stack.pop_back();
} |
attr always_comb_or_latch {
AstNode *node = new AstNode(AST_ALWAYS);
append_attr(node, $1);
if ($2)
node->attributes[ID(always_latch)] = AstNode::mkconst_int(1, false);
else
node->attributes[ID(always_comb)] = AstNode::mkconst_int(1, false);
ast_stack.back()->children.push_back(node);
ast_stack.push_back(node);
AstNode *block = new AstNode(AST_BLOCK);
ast_stack.back()->children.push_back(block);
ast_stack.push_back(block);
} behavioral_stmt {
ast_stack.pop_back();
ast_stack.pop_back();
} |
attr TOK_INITIAL {
AstNode *node = new AstNode(AST_INITIAL);
append_attr(node, $1);
@ -1823,7 +1916,7 @@ simple_behavioral_stmt:
// this production creates the obligatory if-else shift/reduce conflict
behavioral_stmt:
defattr | assert | wire_decl | param_decl | localparam_decl |
defattr | assert | wire_decl | param_decl | localparam_decl | typedef_decl |
non_opt_delay behavioral_stmt |
simple_behavioral_stmt ';' | ';' |
hierarchical_id attr {

View File

@ -551,6 +551,10 @@ void log_dump_val_worker(RTLIL::SigSpec v) {
log("%s", log_signal(v));
}
void log_dump_val_worker(RTLIL::State v) {
log("%s", log_signal(v));
}
const char *log_signal(const RTLIL::SigSpec &sig, bool autoint)
{
std::stringstream buf;

View File

@ -292,6 +292,7 @@ static inline void log_dump_val_worker(PerformanceTimer p) { log("%f seconds", p
static inline void log_dump_args_worker(const char *p YS_ATTRIBUTE(unused)) { log_assert(*p == 0); }
void log_dump_val_worker(RTLIL::IdString v);
void log_dump_val_worker(RTLIL::SigSpec v);
void log_dump_val_worker(RTLIL::State v);
template<typename K, typename T, typename OPS>
static inline void log_dump_val_worker(dict<K, T, OPS> &v) {

View File

@ -439,7 +439,7 @@ void Frontend::execute(std::vector<std::string> args, RTLIL::Design *design)
FILE *Frontend::current_script_file = NULL;
std::string Frontend::last_here_document;
void Frontend::extra_args(std::istream *&f, std::string &filename, std::vector<std::string> args, size_t argidx)
void Frontend::extra_args(std::istream *&f, std::string &filename, std::vector<std::string> args, size_t argidx, bool bin_input)
{
bool called_with_fp = f != NULL;
@ -489,7 +489,7 @@ void Frontend::extra_args(std::istream *&f, std::string &filename, std::vector<s
next_args.insert(next_args.end(), filenames.begin()+1, filenames.end());
}
std::ifstream *ff = new std::ifstream;
ff->open(filename.c_str());
ff->open(filename.c_str(), bin_input ? std::ifstream::binary : std::ifstream::in);
yosys_input_files.insert(filename);
if (ff->fail())
delete ff;
@ -612,7 +612,7 @@ void Backend::execute(std::vector<std::string> args, RTLIL::Design *design)
delete f;
}
void Backend::extra_args(std::ostream *&f, std::string &filename, std::vector<std::string> args, size_t argidx)
void Backend::extra_args(std::ostream *&f, std::string &filename, std::vector<std::string> args, size_t argidx, bool bin_output)
{
bool called_with_fp = f != NULL;
@ -647,7 +647,7 @@ void Backend::extra_args(std::ostream *&f, std::string &filename, std::vector<st
#endif
} else {
std::ofstream *ff = new std::ofstream;
ff->open(filename.c_str(), std::ofstream::trunc);
ff->open(filename.c_str(), bin_output ? (std::ofstream::trunc | std::ofstream::binary) : std::ofstream::trunc);
yosys_output_files.insert(filename);
if (ff->fail()) {
delete ff;

View File

@ -94,7 +94,7 @@ struct Frontend : Pass
virtual void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) = 0;
static std::vector<std::string> next_args;
void extra_args(std::istream *&f, std::string &filename, std::vector<std::string> args, size_t argidx);
void extra_args(std::istream *&f, std::string &filename, std::vector<std::string> args, size_t argidx, bool bin_input = false);
static void frontend_call(RTLIL::Design *design, std::istream *f, std::string filename, std::string command);
static void frontend_call(RTLIL::Design *design, std::istream *f, std::string filename, std::vector<std::string> args);
@ -109,7 +109,7 @@ struct Backend : Pass
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE YS_FINAL;
virtual void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) = 0;
void extra_args(std::ostream *&f, std::string &filename, std::vector<std::string> args, size_t argidx);
void extra_args(std::ostream *&f, std::string &filename, std::vector<std::string> args, size_t argidx, bool bin_output = false);
static void backend_call(RTLIL::Design *design, std::ostream *f, std::string filename, std::string command);
static void backend_call(RTLIL::Design *design, std::ostream *f, std::string filename, std::vector<std::string> args);

View File

@ -3083,6 +3083,7 @@ void RTLIL::SigSpec::replace(const dict<RTLIL::SigBit, RTLIL::SigBit> &rules, RT
log_assert(other != NULL);
log_assert(width_ == other->width_);
if (rules.empty()) return;
unpack();
other->unpack();
@ -3107,6 +3108,7 @@ void RTLIL::SigSpec::replace(const std::map<RTLIL::SigBit, RTLIL::SigBit> &rules
log_assert(other != NULL);
log_assert(width_ == other->width_);
if (rules.empty()) return;
unpack();
other->unpack();
@ -3552,6 +3554,12 @@ bool RTLIL::SigSpec::operator ==(const RTLIL::SigSpec &other) const
if (width_ != other.width_)
return false;
// Without this, SigSpec() == SigSpec(State::S0, 0) will fail
// since the RHS will contain one SigChunk of width 0 causing
// the size check below to fail
if (width_ == 0)
return true;
pack();
other.pack();

View File

@ -609,8 +609,11 @@ struct RTLIL::Const
std::string decode_string() const;
inline int size() const { return bits.size(); }
inline bool empty() const { return bits.empty(); }
inline RTLIL::State &operator[](int index) { return bits.at(index); }
inline const RTLIL::State &operator[](int index) const { return bits.at(index); }
inline decltype(bits)::iterator begin() { return bits.begin(); }
inline decltype(bits)::iterator end() { return bits.end(); }
bool is_fully_zero() const;
bool is_fully_ones() const;

View File

@ -210,6 +210,7 @@ namespace RTLIL {
struct Module;
struct Design;
struct Monitor;
enum State : unsigned char;
}
namespace AST {

788
libs/json11/json11.cpp Normal file
View File

@ -0,0 +1,788 @@
/* Copyright (c) 2013 Dropbox, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "json11.hpp"
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <cstdio>
#include <limits>
namespace json11 {
static const int max_depth = 200;
using std::string;
using std::vector;
using std::map;
using std::make_shared;
using std::initializer_list;
using std::move;
/* Helper for representing null - just a do-nothing struct, plus comparison
* operators so the helpers in JsonValue work. We can't use nullptr_t because
* it may not be orderable.
*/
struct NullStruct {
bool operator==(NullStruct) const { return true; }
bool operator<(NullStruct) const { return false; }
};
/* * * * * * * * * * * * * * * * * * * *
* Serialization
*/
static void dump(NullStruct, string &out) {
out += "null";
}
static void dump(double value, string &out) {
if (std::isfinite(value)) {
char buf[32];
snprintf(buf, sizeof buf, "%.17g", value);
out += buf;
} else {
out += "null";
}
}
static void dump(int value, string &out) {
char buf[32];
snprintf(buf, sizeof buf, "%d", value);
out += buf;
}
static void dump(bool value, string &out) {
out += value ? "true" : "false";
}
static void dump(const string &value, string &out) {
out += '"';
for (size_t i = 0; i < value.length(); i++) {
const char ch = value[i];
if (ch == '\\') {
out += "\\\\";
} else if (ch == '"') {
out += "\\\"";
} else if (ch == '\b') {
out += "\\b";
} else if (ch == '\f') {
out += "\\f";
} else if (ch == '\n') {
out += "\\n";
} else if (ch == '\r') {
out += "\\r";
} else if (ch == '\t') {
out += "\\t";
} else if (static_cast<uint8_t>(ch) <= 0x1f) {
char buf[8];
snprintf(buf, sizeof buf, "\\u%04x", ch);
out += buf;
} else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
&& static_cast<uint8_t>(value[i+2]) == 0xa8) {
out += "\\u2028";
i += 2;
} else if (static_cast<uint8_t>(ch) == 0xe2 && static_cast<uint8_t>(value[i+1]) == 0x80
&& static_cast<uint8_t>(value[i+2]) == 0xa9) {
out += "\\u2029";
i += 2;
} else {
out += ch;
}
}
out += '"';
}
static void dump(const Json::array &values, string &out) {
bool first = true;
out += "[";
for (const auto &value : values) {
if (!first)
out += ", ";
value.dump(out);
first = false;
}
out += "]";
}
static void dump(const Json::object &values, string &out) {
bool first = true;
out += "{";
for (const auto &kv : values) {
if (!first)
out += ", ";
dump(kv.first, out);
out += ": ";
kv.second.dump(out);
first = false;
}
out += "}";
}
void Json::dump(string &out) const {
m_ptr->dump(out);
}
/* * * * * * * * * * * * * * * * * * * *
* Value wrappers
*/
template <Json::Type tag, typename T>
class Value : public JsonValue {
protected:
// Constructors
explicit Value(const T &value) : m_value(value) {}
explicit Value(T &&value) : m_value(move(value)) {}
// Get type tag
Json::Type type() const override {
return tag;
}
// Comparisons
bool equals(const JsonValue * other) const override {
return m_value == static_cast<const Value<tag, T> *>(other)->m_value;
}
bool less(const JsonValue * other) const override {
return m_value < static_cast<const Value<tag, T> *>(other)->m_value;
}
const T m_value;
void dump(string &out) const override { json11::dump(m_value, out); }
};
class JsonDouble final : public Value<Json::NUMBER, double> {
double number_value() const override { return m_value; }
int int_value() const override { return static_cast<int>(m_value); }
bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
bool less(const JsonValue * other) const override { return m_value < other->number_value(); }
public:
explicit JsonDouble(double value) : Value(value) {}
};
class JsonInt final : public Value<Json::NUMBER, int> {
double number_value() const override { return m_value; }
int int_value() const override { return m_value; }
bool equals(const JsonValue * other) const override { return m_value == other->number_value(); }
bool less(const JsonValue * other) const override { return m_value < other->number_value(); }
public:
explicit JsonInt(int value) : Value(value) {}
};
class JsonBoolean final : public Value<Json::BOOL, bool> {
bool bool_value() const override { return m_value; }
public:
explicit JsonBoolean(bool value) : Value(value) {}
};
class JsonString final : public Value<Json::STRING, string> {
const string &string_value() const override { return m_value; }
public:
explicit JsonString(const string &value) : Value(value) {}
explicit JsonString(string &&value) : Value(move(value)) {}
};
class JsonArray final : public Value<Json::ARRAY, Json::array> {
const Json::array &array_items() const override { return m_value; }
const Json & operator[](size_t i) const override;
public:
explicit JsonArray(const Json::array &value) : Value(value) {}
explicit JsonArray(Json::array &&value) : Value(move(value)) {}
};
class JsonObject final : public Value<Json::OBJECT, Json::object> {
const Json::object &object_items() const override { return m_value; }
const Json & operator[](const string &key) const override;
public:
explicit JsonObject(const Json::object &value) : Value(value) {}
explicit JsonObject(Json::object &&value) : Value(move(value)) {}
};
class JsonNull final : public Value<Json::NUL, NullStruct> {
public:
JsonNull() : Value({}) {}
};
/* * * * * * * * * * * * * * * * * * * *
* Static globals - static-init-safe
*/
struct Statics {
const std::shared_ptr<JsonValue> null = make_shared<JsonNull>();
const std::shared_ptr<JsonValue> t = make_shared<JsonBoolean>(true);
const std::shared_ptr<JsonValue> f = make_shared<JsonBoolean>(false);
const string empty_string;
const vector<Json> empty_vector;
const map<string, Json> empty_map;
Statics() {}
};
static const Statics & statics() {
static const Statics s {};
return s;
}
static const Json & static_null() {
// This has to be separate, not in Statics, because Json() accesses statics().null.
static const Json json_null;
return json_null;
}
/* * * * * * * * * * * * * * * * * * * *
* Constructors
*/
Json::Json() noexcept : m_ptr(statics().null) {}
Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {}
Json::Json(double value) : m_ptr(make_shared<JsonDouble>(value)) {}
Json::Json(int value) : m_ptr(make_shared<JsonInt>(value)) {}
Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {}
Json::Json(const string &value) : m_ptr(make_shared<JsonString>(value)) {}
Json::Json(string &&value) : m_ptr(make_shared<JsonString>(move(value))) {}
Json::Json(const char * value) : m_ptr(make_shared<JsonString>(value)) {}
Json::Json(const Json::array &values) : m_ptr(make_shared<JsonArray>(values)) {}
Json::Json(Json::array &&values) : m_ptr(make_shared<JsonArray>(move(values))) {}
Json::Json(const Json::object &values) : m_ptr(make_shared<JsonObject>(values)) {}
Json::Json(Json::object &&values) : m_ptr(make_shared<JsonObject>(move(values))) {}
/* * * * * * * * * * * * * * * * * * * *
* Accessors
*/
Json::Type Json::type() const { return m_ptr->type(); }
double Json::number_value() const { return m_ptr->number_value(); }
int Json::int_value() const { return m_ptr->int_value(); }
bool Json::bool_value() const { return m_ptr->bool_value(); }
const string & Json::string_value() const { return m_ptr->string_value(); }
const vector<Json> & Json::array_items() const { return m_ptr->array_items(); }
const map<string, Json> & Json::object_items() const { return m_ptr->object_items(); }
const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; }
const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; }
double JsonValue::number_value() const { return 0; }
int JsonValue::int_value() const { return 0; }
bool JsonValue::bool_value() const { return false; }
const string & JsonValue::string_value() const { return statics().empty_string; }
const vector<Json> & JsonValue::array_items() const { return statics().empty_vector; }
const map<string, Json> & JsonValue::object_items() const { return statics().empty_map; }
const Json & JsonValue::operator[] (size_t) const { return static_null(); }
const Json & JsonValue::operator[] (const string &) const { return static_null(); }
const Json & JsonObject::operator[] (const string &key) const {
auto iter = m_value.find(key);
return (iter == m_value.end()) ? static_null() : iter->second;
}
const Json & JsonArray::operator[] (size_t i) const {
if (i >= m_value.size()) return static_null();
else return m_value[i];
}
/* * * * * * * * * * * * * * * * * * * *
* Comparison
*/
bool Json::operator== (const Json &other) const {
if (m_ptr == other.m_ptr)
return true;
if (m_ptr->type() != other.m_ptr->type())
return false;
return m_ptr->equals(other.m_ptr.get());
}
bool Json::operator< (const Json &other) const {
if (m_ptr == other.m_ptr)
return false;
if (m_ptr->type() != other.m_ptr->type())
return m_ptr->type() < other.m_ptr->type();
return m_ptr->less(other.m_ptr.get());
}
/* * * * * * * * * * * * * * * * * * * *
* Parsing
*/
/* esc(c)
*
* Format char c suitable for printing in an error message.
*/
static inline string esc(char c) {
char buf[12];
if (static_cast<uint8_t>(c) >= 0x20 && static_cast<uint8_t>(c) <= 0x7f) {
snprintf(buf, sizeof buf, "'%c' (%d)", c, c);
} else {
snprintf(buf, sizeof buf, "(%d)", c);
}
return string(buf);
}
static inline bool in_range(long x, long lower, long upper) {
return (x >= lower && x <= upper);
}
namespace {
/* JsonParser
*
* Object that tracks all state of an in-progress parse.
*/
struct JsonParser final {
/* State
*/
const string &str;
size_t i;
string &err;
bool failed;
const JsonParse strategy;
/* fail(msg, err_ret = Json())
*
* Mark this parse as failed.
*/
Json fail(string &&msg) {
return fail(move(msg), Json());
}
template <typename T>
T fail(string &&msg, const T err_ret) {
if (!failed)
err = std::move(msg);
failed = true;
return err_ret;
}
/* consume_whitespace()
*
* Advance until the current character is non-whitespace.
*/
void consume_whitespace() {
while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t')
i++;
}
/* consume_comment()
*
* Advance comments (c-style inline and multiline).
*/
bool consume_comment() {
bool comment_found = false;
if (str[i] == '/') {
i++;
if (i == str.size())
return fail("unexpected end of input after start of comment", false);
if (str[i] == '/') { // inline comment
i++;
// advance until next line, or end of input
while (i < str.size() && str[i] != '\n') {
i++;
}
comment_found = true;
}
else if (str[i] == '*') { // multiline comment
i++;
if (i > str.size()-2)
return fail("unexpected end of input inside multi-line comment", false);
// advance until closing tokens
while (!(str[i] == '*' && str[i+1] == '/')) {
i++;
if (i > str.size()-2)
return fail(
"unexpected end of input inside multi-line comment", false);
}
i += 2;
comment_found = true;
}
else
return fail("malformed comment", false);
}
return comment_found;
}
/* consume_garbage()
*
* Advance until the current character is non-whitespace and non-comment.
*/
void consume_garbage() {
consume_whitespace();
if(strategy == JsonParse::COMMENTS) {
bool comment_found = false;
do {
comment_found = consume_comment();
if (failed) return;
consume_whitespace();
}
while(comment_found);
}
}
/* get_next_token()
*
* Return the next non-whitespace character. If the end of the input is reached,
* flag an error and return 0.
*/
char get_next_token() {
consume_garbage();
if (failed) return static_cast<char>(0);
if (i == str.size())
return fail("unexpected end of input", static_cast<char>(0));
return str[i++];
}
/* encode_utf8(pt, out)
*
* Encode pt as UTF-8 and add it to out.
*/
void encode_utf8(long pt, string & out) {
if (pt < 0)
return;
if (pt < 0x80) {
out += static_cast<char>(pt);
} else if (pt < 0x800) {
out += static_cast<char>((pt >> 6) | 0xC0);
out += static_cast<char>((pt & 0x3F) | 0x80);
} else if (pt < 0x10000) {
out += static_cast<char>((pt >> 12) | 0xE0);
out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
out += static_cast<char>((pt & 0x3F) | 0x80);
} else {
out += static_cast<char>((pt >> 18) | 0xF0);
out += static_cast<char>(((pt >> 12) & 0x3F) | 0x80);
out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
out += static_cast<char>((pt & 0x3F) | 0x80);
}
}
/* parse_string()
*
* Parse a string, starting at the current position.
*/
string parse_string() {
string out;
long last_escaped_codepoint = -1;
while (true) {
if (i == str.size())
return fail("unexpected end of input in string", "");
char ch = str[i++];
if (ch == '"') {
encode_utf8(last_escaped_codepoint, out);
return out;
}
if (in_range(ch, 0, 0x1f))
return fail("unescaped " + esc(ch) + " in string", "");
// The usual case: non-escaped characters
if (ch != '\\') {
encode_utf8(last_escaped_codepoint, out);
last_escaped_codepoint = -1;
out += ch;
continue;
}
// Handle escapes
if (i == str.size())
return fail("unexpected end of input in string", "");
ch = str[i++];
if (ch == 'u') {
// Extract 4-byte escape sequence
string esc = str.substr(i, 4);
// Explicitly check length of the substring. The following loop
// relies on std::string returning the terminating NUL when
// accessing str[length]. Checking here reduces brittleness.
if (esc.length() < 4) {
return fail("bad \\u escape: " + esc, "");
}
for (size_t j = 0; j < 4; j++) {
if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F')
&& !in_range(esc[j], '0', '9'))
return fail("bad \\u escape: " + esc, "");
}
long codepoint = strtol(esc.data(), nullptr, 16);
// JSON specifies that characters outside the BMP shall be encoded as a pair
// of 4-hex-digit \u escapes encoding their surrogate pair components. Check
// whether we're in the middle of such a beast: the previous codepoint was an
// escaped lead (high) surrogate, and this is a trail (low) surrogate.
if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF)
&& in_range(codepoint, 0xDC00, 0xDFFF)) {
// Reassemble the two surrogate pairs into one astral-plane character, per
// the UTF-16 algorithm.
encode_utf8((((last_escaped_codepoint - 0xD800) << 10)
| (codepoint - 0xDC00)) + 0x10000, out);
last_escaped_codepoint = -1;
} else {
encode_utf8(last_escaped_codepoint, out);
last_escaped_codepoint = codepoint;
}
i += 4;
continue;
}
encode_utf8(last_escaped_codepoint, out);
last_escaped_codepoint = -1;
if (ch == 'b') {
out += '\b';
} else if (ch == 'f') {
out += '\f';
} else if (ch == 'n') {
out += '\n';
} else if (ch == 'r') {
out += '\r';
} else if (ch == 't') {
out += '\t';
} else if (ch == '"' || ch == '\\' || ch == '/') {
out += ch;
} else {
return fail("invalid escape character " + esc(ch), "");
}
}
}
/* parse_number()
*
* Parse a double.
*/
Json parse_number() {
size_t start_pos = i;
if (str[i] == '-')
i++;
// Integer part
if (str[i] == '0') {
i++;
if (in_range(str[i], '0', '9'))
return fail("leading 0s not permitted in numbers");
} else if (in_range(str[i], '1', '9')) {
i++;
while (in_range(str[i], '0', '9'))
i++;
} else {
return fail("invalid " + esc(str[i]) + " in number");
}
if (str[i] != '.' && str[i] != 'e' && str[i] != 'E'
&& (i - start_pos) <= static_cast<size_t>(std::numeric_limits<int>::digits10)) {
return std::atoi(str.c_str() + start_pos);
}
// Decimal part
if (str[i] == '.') {
i++;
if (!in_range(str[i], '0', '9'))
return fail("at least one digit required in fractional part");
while (in_range(str[i], '0', '9'))
i++;
}
// Exponent part
if (str[i] == 'e' || str[i] == 'E') {
i++;
if (str[i] == '+' || str[i] == '-')
i++;
if (!in_range(str[i], '0', '9'))
return fail("at least one digit required in exponent");
while (in_range(str[i], '0', '9'))
i++;
}
return std::strtod(str.c_str() + start_pos, nullptr);
}
/* expect(str, res)
*
* Expect that 'str' starts at the character that was just read. If it does, advance
* the input and return res. If not, flag an error.
*/
Json expect(const string &expected, Json res) {
assert(i != 0);
i--;
if (str.compare(i, expected.length(), expected) == 0) {
i += expected.length();
return res;
} else {
return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length()));
}
}
/* parse_json()
*
* Parse a JSON object.
*/
Json parse_json(int depth) {
if (depth > max_depth) {
return fail("exceeded maximum nesting depth");
}
char ch = get_next_token();
if (failed)
return Json();
if (ch == '-' || (ch >= '0' && ch <= '9')) {
i--;
return parse_number();
}
if (ch == 't')
return expect("true", true);
if (ch == 'f')
return expect("false", false);
if (ch == 'n')
return expect("null", Json());
if (ch == '"')
return parse_string();
if (ch == '{') {
map<string, Json> data;
ch = get_next_token();
if (ch == '}')
return data;
while (1) {
if (ch != '"')
return fail("expected '\"' in object, got " + esc(ch));
string key = parse_string();
if (failed)
return Json();
ch = get_next_token();
if (ch != ':')
return fail("expected ':' in object, got " + esc(ch));
data[std::move(key)] = parse_json(depth + 1);
if (failed)
return Json();
ch = get_next_token();
if (ch == '}')
break;
if (ch != ',')
return fail("expected ',' in object, got " + esc(ch));
ch = get_next_token();
}
return data;
}
if (ch == '[') {
vector<Json> data;
ch = get_next_token();
if (ch == ']')
return data;
while (1) {
i--;
data.push_back(parse_json(depth + 1));
if (failed)
return Json();
ch = get_next_token();
if (ch == ']')
break;
if (ch != ',')
return fail("expected ',' in list, got " + esc(ch));
ch = get_next_token();
(void)ch;
}
return data;
}
return fail("expected value, got " + esc(ch));
}
};
}//namespace {
Json Json::parse(const string &in, string &err, JsonParse strategy) {
JsonParser parser { in, 0, err, false, strategy };
Json result = parser.parse_json(0);
// Check for any trailing garbage
parser.consume_garbage();
if (parser.failed)
return Json();
if (parser.i != in.size())
return parser.fail("unexpected trailing " + esc(in[parser.i]));
return result;
}
// Documented in json11.hpp
vector<Json> Json::parse_multi(const string &in,
std::string::size_type &parser_stop_pos,
string &err,
JsonParse strategy) {
JsonParser parser { in, 0, err, false, strategy };
parser_stop_pos = 0;
vector<Json> json_vec;
while (parser.i != in.size() && !parser.failed) {
json_vec.push_back(parser.parse_json(0));
if (parser.failed)
break;
// Check for another object
parser.consume_garbage();
if (parser.failed)
break;
parser_stop_pos = parser.i;
}
return json_vec;
}
/* * * * * * * * * * * * * * * * * * * *
* Shape-checking
*/
bool Json::has_shape(const shape & types, string & err) const {
if (!is_object()) {
err = "expected JSON object, got " + dump();
return false;
}
for (auto & item : types) {
if ((*this)[item.first].type() != item.second) {
err = "bad type for " + item.first + " in " + dump();
return false;
}
}
return true;
}
} // namespace json11

232
libs/json11/json11.hpp Normal file
View File

@ -0,0 +1,232 @@
/* json11
*
* json11 is a tiny JSON library for C++11, providing JSON parsing and serialization.
*
* The core object provided by the library is json11::Json. A Json object represents any JSON
* value: null, bool, number (int or double), string (std::string), array (std::vector), or
* object (std::map).
*
* Json objects act like values: they can be assigned, copied, moved, compared for equality or
* order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and
* Json::parse (static) to parse a std::string as a Json object.
*
* Internally, the various types of Json object are represented by the JsonValue class
* hierarchy.
*
* A note on numbers - JSON specifies the syntax of number formatting but not its semantics,
* so some JSON implementations distinguish between integers and floating-point numbers, while
* some don't. In json11, we choose the latter. Because some JSON implementations (namely
* Javascript itself) treat all numbers as the same type, distinguishing the two leads
* to JSON that will be *silently* changed by a round-trip through those implementations.
* Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also
* provides integer helpers.
*
* Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the
* range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64
* or long long to avoid the Y2038K problem; a double storing microseconds since some epoch
* will be exact for +/- 275 years.)
*/
/* Copyright (c) 2013 Dropbox, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <initializer_list>
#ifdef _MSC_VER
#if _MSC_VER <= 1800 // VS 2013
#ifndef noexcept
#define noexcept throw()
#endif
#ifndef snprintf
#define snprintf _snprintf_s
#endif
#endif
#endif
namespace json11 {
enum JsonParse {
STANDARD, COMMENTS
};
class JsonValue;
class Json final {
public:
// Types
enum Type {
NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT
};
// Array and object typedefs
typedef std::vector<Json> array;
typedef std::map<std::string, Json> object;
// Constructors for the various types of JSON value.
Json() noexcept; // NUL
Json(std::nullptr_t) noexcept; // NUL
Json(double value); // NUMBER
Json(int value); // NUMBER
Json(bool value); // BOOL
Json(const std::string &value); // STRING
Json(std::string &&value); // STRING
Json(const char * value); // STRING
Json(const array &values); // ARRAY
Json(array &&values); // ARRAY
Json(const object &values); // OBJECT
Json(object &&values); // OBJECT
// Implicit constructor: anything with a to_json() function.
template <class T, class = decltype(&T::to_json)>
Json(const T & t) : Json(t.to_json()) {}
// Implicit constructor: map-like objects (std::map, std::unordered_map, etc)
template <class M, typename std::enable_if<
std::is_constructible<std::string, decltype(std::declval<M>().begin()->first)>::value
&& std::is_constructible<Json, decltype(std::declval<M>().begin()->second)>::value,
int>::type = 0>
Json(const M & m) : Json(object(m.begin(), m.end())) {}
// Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc)
template <class V, typename std::enable_if<
std::is_constructible<Json, decltype(*std::declval<V>().begin())>::value,
int>::type = 0>
Json(const V & v) : Json(array(v.begin(), v.end())) {}
// This prevents Json(some_pointer) from accidentally producing a bool. Use
// Json(bool(some_pointer)) if that behavior is desired.
Json(void *) = delete;
// Accessors
Type type() const;
bool is_null() const { return type() == NUL; }
bool is_number() const { return type() == NUMBER; }
bool is_bool() const { return type() == BOOL; }
bool is_string() const { return type() == STRING; }
bool is_array() const { return type() == ARRAY; }
bool is_object() const { return type() == OBJECT; }
// Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not
// distinguish between integer and non-integer numbers - number_value() and int_value()
// can both be applied to a NUMBER-typed object.
double number_value() const;
int int_value() const;
// Return the enclosed value if this is a boolean, false otherwise.
bool bool_value() const;
// Return the enclosed string if this is a string, "" otherwise.
const std::string &string_value() const;
// Return the enclosed std::vector if this is an array, or an empty vector otherwise.
const array &array_items() const;
// Return the enclosed std::map if this is an object, or an empty map otherwise.
const object &object_items() const;
// Return a reference to arr[i] if this is an array, Json() otherwise.
const Json & operator[](size_t i) const;
// Return a reference to obj[key] if this is an object, Json() otherwise.
const Json & operator[](const std::string &key) const;
// Serialize.
void dump(std::string &out) const;
std::string dump() const {
std::string out;
dump(out);
return out;
}
// Parse. If parse fails, return Json() and assign an error message to err.
static Json parse(const std::string & in,
std::string & err,
JsonParse strategy = JsonParse::STANDARD);
static Json parse(const char * in,
std::string & err,
JsonParse strategy = JsonParse::STANDARD) {
if (in) {
return parse(std::string(in), err, strategy);
} else {
err = "null input";
return nullptr;
}
}
// Parse multiple objects, concatenated or separated by whitespace
static std::vector<Json> parse_multi(
const std::string & in,
std::string::size_type & parser_stop_pos,
std::string & err,
JsonParse strategy = JsonParse::STANDARD);
static inline std::vector<Json> parse_multi(
const std::string & in,
std::string & err,
JsonParse strategy = JsonParse::STANDARD) {
std::string::size_type parser_stop_pos;
return parse_multi(in, parser_stop_pos, err, strategy);
}
bool operator== (const Json &rhs) const;
bool operator< (const Json &rhs) const;
bool operator!= (const Json &rhs) const { return !(*this == rhs); }
bool operator<= (const Json &rhs) const { return !(rhs < *this); }
bool operator> (const Json &rhs) const { return (rhs < *this); }
bool operator>= (const Json &rhs) const { return !(*this < rhs); }
/* has_shape(types, err)
*
* Return true if this is a JSON object and, for each item in types, has a field of
* the given type. If not, return false and set err to a descriptive message.
*/
typedef std::initializer_list<std::pair<std::string, Type>> shape;
bool has_shape(const shape & types, std::string & err) const;
private:
std::shared_ptr<JsonValue> m_ptr;
};
// Internal class hierarchy - JsonValue objects are not exposed to users of this API.
class JsonValue {
protected:
friend class Json;
friend class JsonInt;
friend class JsonDouble;
virtual Json::Type type() const = 0;
virtual bool equals(const JsonValue * other) const = 0;
virtual bool less(const JsonValue * other) const = 0;
virtual void dump(std::string &out) const = 0;
virtual double number_value() const;
virtual int int_value() const;
virtual bool bool_value() const;
virtual const std::string &string_value() const;
virtual const Json::array &array_items() const;
virtual const Json &operator[](size_t i) const;
virtual const Json::object &object_items() const;
virtual const Json &operator[](const std::string &key) const;
virtual ~JsonValue() {}
};
} // namespace json11

View File

@ -253,6 +253,8 @@ class WContainer:
candidate = WType.from_string(arg.strip(), containing_file, line_number)
if candidate == None:
return None
if candidate.name == "void":
return None
cont.args.append(candidate)
return cont
@ -880,11 +882,8 @@ class WClass:
text += fun.gen_def_virtual()
return text
def gen_boost_py(self):
text = "\n\t\tclass_<" + self.name
if self.link_type == link_types.derive:
text += "Wrap, boost::noncopyable"
text += ">(\"" + self.name + "\""
def gen_boost_py_body(self):
text = ""
if self.printable_constrs() == 0 or not self.contains_default_constr():
text += ", no_init"
text += ")"
@ -907,6 +906,21 @@ class WClass:
text += "\n\t\t\t;\n"
return text
def gen_boost_py(self):
body = self.gen_boost_py_body()
if self.link_type == link_types.derive:
text = "\n\t\tclass_<" + self.name + ">(\"Cpp" + self.name + "\""
text += body
text += "\n\t\tclass_<" + self.name
text += "Wrap, boost::noncopyable"
text += ">(\"" + self.name + "\""
text += body
else:
text = "\n\t\tclass_<" + self.name + ">(\"" + self.name + "\""
text += body
return text
def contains_default_constr(self):
for c in self.found_constrs:
if len(c.args) == 0:
@ -974,6 +988,7 @@ blacklist_methods = ["YOSYS_NAMESPACE::Pass::run_register", "YOSYS_NAMESPACE::Mo
enum_names = ["State","SyncType","ConstFlags"]
enums = [] #Do not edit
glbls = []
unowned_functions = []
@ -1081,6 +1096,8 @@ class WConstructor:
con.args = []
con.duplicate = False
con.protected = protected
if str.startswith(str_def, "inline "):
str_def = str_def[7:]
if not str.startswith(str_def, class_.name + "("):
return None
str_def = str_def[len(class_.name)+1:]
@ -1721,6 +1738,159 @@ class WMember:
text += ")"
return text
class WGlobal:
orig_text = None
wtype = attr_types.default
name = None
containing_file = None
namespace = ""
is_const = False
def from_string(str_def, containing_file, line_number, namespace):
glbl = WGlobal()
glbl.orig_text = str_def
glbl.wtype = None
glbl.name = ""
glbl.containing_file = containing_file
glbl.namespace = namespace
glbl.is_const = False
if not str.startswith(str_def, "extern"):
return None
str_def = str_def[7:]
if str.startswith(str_def, "const "):
glbl.is_const = True
str_def = str_def[6:]
if str_def.count(" ") == 0:
return None
parts = split_list(str_def.strip(), " ")
prefix = ""
i = 0
for part in parts:
if part in ["unsigned", "long", "short"]:
prefix += part + " "
i += 1
else:
break
parts = parts[i:]
if len(parts) <= 1:
return None
glbl.wtype = WType.from_string(prefix + parts[0], containing_file, line_number)
if glbl.wtype == None:
return None
str_def = parts[1]
for part in parts[2:]:
str_def = str_def + " " + part
if str_def.find("(") != -1 or str_def.find(")") != -1 or str_def.find("{") != -1 or str_def.find("}") != -1:
return None
found = str_def.find(";")
if found == -1:
return None
found_eq = str_def.find("=")
if found_eq != -1:
found = found_eq
glbl.name = str_def[:found]
str_def = str_def[found+1:]
if glbl.name.find("*") == 0:
glbl.name = glbl.name.replace("*", "")
glbl.wtype.attr_type = attr_types.star
if glbl.name.find("&&") == 0:
glbl.name = glbl.name.replace("&&", "")
glbl.wtype.attr_type = attr_types.ampamp
if glbl.name.find("&") == 0:
glbl.name = glbl.name.replace("&", "")
glbl.wtype.attr_type = attr_types.amp
if(len(str_def.strip()) != 0):
return None
if len(glbl.name.split(",")) > 1:
glbl_list = []
for name in glbl.name.split(","):
name = name.strip();
glbl_list.append(WGlobal())
glbl_list[-1].orig_text = glbl.orig_text
glbl_list[-1].wtype = glbl.wtype
glbl_list[-1].name = name
glbl_list[-1].containing_file = glbl.containing_file
glbl_list[-1].namespace = glbl.namespace
glbl_list[-1].is_const = glbl.is_const
return glbl_list
return glbl
def gen_def(self):
text = "\n\t"
if self.is_const:
text += "const "
text += self.wtype.gen_text() + " get_var_py_" + self.name + "()"
text += "\n\t{\n\t\t"
if self.wtype.attr_type == attr_types.star:
text += "if(" + self.namespace + "::" + self.name + " == NULL)\n\t\t\t"
text += "throw std::runtime_error(\"" + self.namespace + "::" + self.name + " is NULL\");\n\t\t"
if self.wtype.name in known_containers:
text += self.wtype.gen_text_cpp()
else:
if self.is_const:
text += "const "
text += self.wtype.gen_text()
if self.wtype.name in classnames or (self.wtype.name in known_containers and self.wtype.attr_type == attr_types.star):
text += "*"
text += " ret_ = "
if self.wtype.name in classnames:
text += self.wtype.name + "::get_py_obj("
if self.wtype.attr_type != attr_types.star:
text += "&"
text += self.namespace + "::" + self.name
if self.wtype.name in classnames:
text += ")"
text += ";"
if self.wtype.name in classnames:
text += "\n\t\treturn *ret_;"
elif self.wtype.name in known_containers:
text += known_containers[self.wtype.name].translate_cpp("ret_", self.wtype.cont.args, "\n\t\t", self.wtype.attr_type == attr_types.star)
text += "\n\t\treturn ret____tmp;"
else:
text += "\n\t\treturn ret_;"
text += "\n\t}\n"
if self.is_const:
return text
ret = Attribute(self.wtype, "rhs");
if self.wtype.name in classnames:
text += "\n\tvoid set_var_py_" + self.name + "(" + self.wtype.gen_text() + " *rhs)"
else:
text += "\n\tvoid set_var_py_" + self.name + "(" + self.wtype.gen_text() + " rhs)"
text += "\n\t{"
text += ret.gen_translation()
text += "\n\t\t" + self.namespace + "::" + self.name + " = " + ret.gen_call() + ";"
text += "\n\t}\n"
return text;
def gen_boost_py(self):
text = "\n\t\t\t.add_static_property(\"" + self.name + "\", &" + "YOSYS_PYTHON::get_var_py_" + self.name
if not self.is_const:
text += ", &YOSYS_PYTHON::set_var_py_" + self.name
text += ")"
return text
def concat_namespace(tuple_list):
if len(tuple_list) == 0:
return ""
@ -1857,6 +2027,16 @@ def parse_header(source):
else:
debug("\t\tFound member \"" + candidate.name + "\" of class \"" + class_[0].name + "\" of type \"" + candidate.wtype.name + "\"", 2)
class_[0].found_vars.append(candidate)
if candidate == None and class_ == None:
candidate = WGlobal.from_string(ugly_line, source.name, i, concat_namespace(namespaces))
if candidate != None:
if type(candidate) == list:
for c in candidate:
glbls.append(c)
debug("\tFound global \"" + c.name + "\" in namespace " + concat_namespace(namespaces), 2)
else:
glbls.append(candidate)
debug("\tFound global \"" + candidate.name + "\" in namespace " + concat_namespace(namespaces), 2)
j = i
line = unpretty_string(line)
@ -1886,6 +2066,17 @@ def parse_header(source):
debug("\t\tFound constructor of class \"" + class_[0].name + "\" in namespace " + concat_namespace(namespaces),2)
class_[0].found_constrs.append(candidate)
continue
if class_ == None:
candidate = WGlobal.from_string(line, source.name, i, concat_namespace(namespaces))
if candidate != None:
if type(candidate) == list:
for c in candidate:
glbls.append(c)
debug("\tFound global \"" + c.name + "\" in namespace " + concat_namespace(namespaces), 2)
else:
glbls.append(candidate)
debug("\tFound global \"" + candidate.name + "\" in namespace " + concat_namespace(namespaces), 2)
continue
if candidate != None:
while i < j:
i += 1
@ -1988,6 +2179,7 @@ def gen_wrappers(filename, debug_level_ = 0):
if len(class_.found_constrs) == 0:
class_.found_constrs.append(WConstructor(source.name, class_))
debug(str(len(unowned_functions)) + " functions are unowned", 1)
debug(str(len(unowned_functions)) + " global variables", 1)
for enum in enums:
debug("Enum " + assure_length(enum.name, len(max(enum_names, key=len)), True) + " contains " + assure_length(str(len(enum.values)), 2, False) + " values", 1)
debug("-"*col, 1)
@ -2023,10 +2215,15 @@ def gen_wrappers(filename, debug_level_ = 0):
#include <boost/python/wrapper.hpp>
#include <boost/python/call.hpp>
#include <boost/python.hpp>
#include <iosfwd> // std::streamsize
#include <iostream>
#include <boost/iostreams/concepts.hpp> // boost::iostreams::sink
#include <boost/iostreams/stream.hpp>
USING_YOSYS_NAMESPACE
namespace YOSYS_PYTHON {
struct YosysStatics{};
""")
for source in sources:
@ -2048,6 +2245,9 @@ namespace YOSYS_PYTHON {
for fun in unowned_functions:
wrapper_file.write(fun.gen_def())
for glbl in glbls:
wrapper_file.write(glbl.gen_def())
wrapper_file.write(""" struct Initializer
{
Initializer() {
@ -2066,12 +2266,89 @@ namespace YOSYS_PYTHON {
}
};
/// source: https://stackoverflow.com/questions/26033781/converting-python-io-object-to-stdostream-when-using-boostpython?noredirect=1&lq=1
/// @brief Type that implements the Boost.IOStream's Sink and Flushable
/// concept for writing data to Python object that support:
/// n = object.write(str) # n = None or bytes written
/// object.flush() # if flush exists, then it is callable
class PythonOutputDevice
{
public:
// This class models both the Sink and Flushable concepts.
struct category
: boost::iostreams::sink_tag,
boost::iostreams::flushable_tag
{};
explicit
PythonOutputDevice(boost::python::object object)
: object_(object)
{}
// Sink concept.
public:
typedef char char_type;
std::streamsize write(const char* buffer, std::streamsize buffer_size)
{
namespace python = boost::python;
// Copy the buffer to a python string.
python::str data(buffer, buffer_size);
// Invoke write on the python object, passing in the data. The following
// is equivalent to:
// n = object_.write(data)
python::extract<std::streamsize> bytes_written(
object_.attr("write")(data));
// Per the Sink concept, return the number of bytes written. If the
// Python return value provides a numeric result, then use it. Otherwise,
// such as the case of a File object, use the buffer_size.
return bytes_written.check()
? bytes_written
: buffer_size;
}
// Flushable concept.
public:
bool flush()
{
// If flush exists, then call it.
boost::python::object flush = object_.attr("flush");
if (!flush.is_none())
{
flush();
}
// Always return true. If an error occurs, an exception should be thrown.
return true;
}
private:
boost::python::object object_;
};
/// @brief Use an auxiliary function to adapt the legacy function.
void log_to_stream(boost::python::object object)
{
// Create an ostream that delegates to the python object.
boost::iostreams::stream<PythonOutputDevice>* output = new boost::iostreams::stream<PythonOutputDevice>(object);
Yosys::log_streams.insert(Yosys::log_streams.begin(), output);
};
BOOST_PYTHON_MODULE(libyosys)
{
using namespace boost::python;
class_<Initializer>("Initializer");
scope().attr("_hidden") = new Initializer();
def("log_to_stream", &log_to_stream);
""")
for enum in enums:
@ -2084,6 +2361,11 @@ namespace YOSYS_PYTHON {
for fun in unowned_functions:
wrapper_file.write(fun.gen_boost_py())
wrapper_file.write("\n\n\t\tclass_<YosysStatics>(\"Yosys\")\n")
for glbl in glbls:
wrapper_file.write(glbl.gen_boost_py())
wrapper_file.write("\t\t;\n")
wrapper_file.write("\n\t}\n}\n#endif")
def print_includes():

View File

@ -5,6 +5,7 @@ OBJS += passes/cmds/design.o
OBJS += passes/cmds/select.o
OBJS += passes/cmds/show.o
OBJS += passes/cmds/rename.o
OBJS += passes/cmds/autoname.o
OBJS += passes/cmds/connect.o
OBJS += passes/cmds/scatter.o
OBJS += passes/cmds/setundef.o

134
passes/cmds/autoname.cc Normal file
View File

@ -0,0 +1,134 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "kernel/yosys.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
int autoname_worker(Module *module)
{
dict<Cell*, pair<int, IdString>> proposed_cell_names;
dict<Wire*, pair<int, IdString>> proposed_wire_names;
dict<Wire*, int> wire_score;
int best_score = -1;
for (auto cell : module->selected_cells())
for (auto &conn : cell->connections())
for (auto bit : conn.second)
if (bit.wire != nullptr)
wire_score[bit.wire]++;
for (auto cell : module->selected_cells()) {
if (cell->name[0] == '$') {
for (auto &conn : cell->connections()) {
string suffix = stringf("_%s_%s", log_id(cell->type), log_id(conn.first));
for (auto bit : conn.second)
if (bit.wire != nullptr && bit.wire->name[0] != '$') {
IdString new_name(bit.wire->name.str() + suffix);
int score = wire_score.at(bit.wire);
if (cell->output(conn.first)) score = 0;
score = 10000*score + new_name.size();
if (!proposed_cell_names.count(cell) || score < proposed_cell_names.at(cell).first) {
if (best_score < 0 || score < best_score)
best_score = score;
proposed_cell_names[cell] = make_pair(score, new_name);
}
}
}
} else {
for (auto &conn : cell->connections()) {
string suffix = stringf("_%s", log_id(conn.first));
for (auto bit : conn.second)
if (bit.wire != nullptr && bit.wire->name[0] == '$') {
IdString new_name(cell->name.str() + suffix);
int score = wire_score.at(bit.wire);
if (cell->output(conn.first)) score = 0;
score = 10000*score + new_name.size();
if (!proposed_wire_names.count(bit.wire) || score < proposed_wire_names.at(bit.wire).first) {
if (best_score < 0 || score < best_score)
best_score = score;
proposed_wire_names[bit.wire] = make_pair(score, new_name);
}
}
}
}
}
for (auto &it : proposed_cell_names) {
if (best_score*2 < it.second.first)
continue;
IdString n = module->uniquify(it.second.second);
log_debug("Rename cell %s in %s to %s.\n", log_id(it.first), log_id(module), log_id(n));
module->rename(it.first, n);
}
for (auto &it : proposed_wire_names) {
if (best_score*2 < it.second.first)
continue;
IdString n = module->uniquify(it.second.second);
log_debug("Rename wire %s in %s to %s.\n", log_id(it.first), log_id(module), log_id(n));
module->rename(it.first, n);
}
return proposed_cell_names.size() + proposed_wire_names.size();
}
struct AutonamePass : public Pass {
AutonamePass() : Pass("autoname", "automatically assign names to objects") { }
void help() YS_OVERRIDE
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" autoname [selection]\n");
log("\n");
log("Assign auto-generated public names to objects with private names (the ones\n");
log("with $-prefix).\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
{
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
{
// if (args[argidx] == "-foo") {
// foo = true;
// continue;
// }
break;
}
log_header(design, "Executing AUTONAME pass.\n");
for (auto module : design->selected_modules())
{
int count = 0, iter = 0;
while (1) {
iter++;
int n = autoname_worker(module);
if (!n) break;
count += n;
}
if (count > 0)
log("Renamed %d objects in module %s (%d iterations).\n", count, log_id(module), iter);
}
}
} AutonamePass;
PRIVATE_NAMESPACE_END

View File

@ -41,14 +41,24 @@ struct CheckPass : public Pass {
log("\n");
log(" - used wires that do not have a driver\n");
log("\n");
log("When called with -noinit then this command also checks for wires which have\n");
log("the 'init' attribute set.\n");
log("Options:\n");
log("\n");
log("When called with -initdrv then this command also checks for wires which have\n");
log("the 'init' attribute set and aren't driven by a FF cell type.\n");
log(" -noinit\n");
log(" Also check for wires which have the 'init' attribute set.\n");
log("\n");
log("When called with -assert then the command will produce an error if any\n");
log("problems are found in the current design.\n");
log(" -initdrv\n");
log(" Also check for wires that have the 'init' attribute set and are not\n");
log(" driven by an FF cell type.\n");
log("\n");
log(" -mapped\n");
log(" Also check for internal cells that have not been mapped to cells of the\n");
log(" target architecture.\n");
log("\n");
log(" -allow-tbuf\n");
log(" Modify the -mapped behavior to still allow $_TBUF_ cells.\n");
log("\n");
log(" -assert\n");
log(" Produce a runtime error if any problems are found in the current design.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
@ -56,6 +66,8 @@ struct CheckPass : public Pass {
int counter = 0;
bool noinit = false;
bool initdrv = false;
bool mapped = false;
bool allow_tbuf = false;
bool assert_mode = false;
size_t argidx;
@ -68,6 +80,14 @@ struct CheckPass : public Pass {
initdrv = true;
continue;
}
if (args[argidx] == "-mapped") {
mapped = true;
continue;
}
if (args[argidx] == "-allow-tbuf") {
allow_tbuf = true;
continue;
}
if (args[argidx] == "-assert") {
assert_mode = true;
continue;
@ -135,6 +155,13 @@ struct CheckPass : public Pass {
TopoSort<string> topo;
for (auto cell : module->cells())
{
if (mapped && cell->type.begins_with("$") && design->module(cell->type) == nullptr) {
if (allow_tbuf && cell->type == ID($_TBUF_)) goto cell_allowed;
log_warning("Cell %s.%s is an unmapped internal cell of type %s.\n", log_id(module), log_id(cell), log_id(cell->type));
counter++;
cell_allowed:;
}
for (auto &conn : cell->connections()) {
SigSpec sig = sigmap(conn.second);
bool logic_cell = yosys_celltypes.cell_evaluable(cell->type);
@ -159,6 +186,7 @@ struct CheckPass : public Pass {
for (auto bit : sig)
if (bit.wire) wire_drivers_count[bit]++;
}
}
pool<SigBit> init_bits;

View File

@ -32,7 +32,8 @@ struct EquivOptPass:public ScriptPass
log("\n");
log(" equiv_opt [options] [command]\n");
log("\n");
log("This command checks circuit equivalence before and after an optimization pass.\n");
log("This command uses temporal induction to check circuit equivalence before and\n");
log("after an optimization pass.\n");
log("\n");
log(" -run <from_label>:<to_label>\n");
log(" only run the commands between the labels (see below). an empty\n");
@ -49,6 +50,9 @@ struct EquivOptPass:public ScriptPass
log(" -multiclock\n");
log(" run clk2fflogic before equivalence checking.\n");
log("\n");
log(" -async2sync\n");
log(" run async2sync before equivalence checking.\n");
log("\n");
log(" -undef\n");
log(" enable modelling of undef states during equiv_induct.\n");
log("\n");
@ -58,7 +62,7 @@ struct EquivOptPass:public ScriptPass
}
std::string command, techmap_opts;
bool assert, undef, multiclock;
bool assert, undef, multiclock, async2sync;
void clear_flags() YS_OVERRIDE
{
@ -67,6 +71,7 @@ struct EquivOptPass:public ScriptPass
assert = false;
undef = false;
multiclock = false;
async2sync = false;
}
void execute(std::vector < std::string > args, RTLIL::Design * design) YS_OVERRIDE
@ -100,6 +105,10 @@ struct EquivOptPass:public ScriptPass
multiclock = true;
continue;
}
if (args[argidx] == "-async2sync") {
async2sync = true;
continue;
}
break;
}
@ -119,6 +128,9 @@ struct EquivOptPass:public ScriptPass
if (!design->full_selection())
log_cmd_error("This command only operates on fully selected designs!\n");
if (async2sync && multiclock)
log_cmd_error("The '-async2sync' and '-multiclock' options are mutually exclusive!\n");
log_header(design, "Executing EQUIV_OPT pass.\n");
log_push();
@ -156,6 +168,8 @@ struct EquivOptPass:public ScriptPass
if (check_label("prove")) {
if (multiclock || help_mode)
run("clk2fflogic", "(only with -multiclock)");
if (async2sync || help_mode)
run("async2sync", " (only with -async2sync)");
run("equiv_make gold gate equiv");
if (help_mode)
run("equiv_induct [-undef] equiv");

View File

@ -158,22 +158,25 @@ static void detect_fsm(RTLIL::Wire *wire)
std::set<sig2driver_entry_t> cellport_list;
sig2user.find(sig_q, cellport_list);
auto sig_q_bits = sig_q.to_sigbit_pool();
for (auto &cellport : cellport_list)
{
RTLIL::Cell *cell = cellport.first;
bool set_output = false, clr_output = false;
if (cell->type == "$ne")
if (cell->type.in("$ne", "$reduce_or", "$reduce_bool"))
set_output = true;
if (cell->type == "$eq")
if (cell->type.in("$eq", "$logic_not", "$reduce_and"))
clr_output = true;
if (!set_output && !clr_output) {
clr_output = true;
if (set_output || clr_output) {
for (auto &port_it : cell->connections())
if (port_it.first != "\\A" || port_it.first != "\\Y")
clr_output = false;
if (cell->input(port_it.first))
for (auto bit : assign_map(port_it.second))
if (bit.wire != nullptr && !sig_q_bits.count(bit))
goto next_cellport;
}
if (set_output || clr_output) {
@ -184,6 +187,7 @@ static void detect_fsm(RTLIL::Wire *wire)
ce.set(sig, val);
}
}
next_cellport:;
}
SigSpec sig_y = sig_d, sig_undef;

View File

@ -35,6 +35,7 @@ struct MemoryPass : public Pass {
log("\n");
log("This pass calls all the other memory_* passes in a useful order:\n");
log("\n");
log(" opt_mem\n");
log(" memory_dff [-nordff] (-memx implies -nordff)\n");
log(" opt_clean\n");
log(" memory_share\n");
@ -81,6 +82,7 @@ struct MemoryPass : public Pass {
}
extra_args(args, argidx, design);
Pass::call(design, "opt_mem");
Pass::call(design, flag_nordff ? "memory_dff -nordff" : "memory_dff");
Pass::call(design, "opt_clean");
Pass::call(design, "memory_share");

View File

@ -1,6 +1,7 @@
OBJS += passes/opt/opt.o
OBJS += passes/opt/opt_merge.o
OBJS += passes/opt/opt_mem.o
OBJS += passes/opt/opt_muxtree.o
OBJS += passes/opt/opt_reduce.o
OBJS += passes/opt/opt_rmdff.o

143
passes/opt/opt_mem.cc Normal file
View File

@ -0,0 +1,143 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "kernel/yosys.h"
#include "kernel/sigtools.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
struct OptMemWorker
{
RTLIL::Design *design;
RTLIL::Module *module;
SigMap sigmap;
bool restart;
dict<IdString, vector<IdString>> memrd, memwr, meminit;
pool<IdString> remove_mem, remove_cells;
OptMemWorker(RTLIL::Module *module) : design(module->design), module(module), sigmap(module), restart(false)
{
for (auto &it : module->memories)
{
memrd[it.first];
memwr[it.first];
meminit[it.first];
}
for (auto cell : module->cells())
{
if (cell->type == ID($memrd)) {
IdString id = cell->getParam(ID(MEMID)).decode_string();
memrd.at(id).push_back(cell->name);
}
if (cell->type == ID($memwr)) {
IdString id = cell->getParam(ID(MEMID)).decode_string();
memwr.at(id).push_back(cell->name);
}
if (cell->type == ID($meminit)) {
IdString id = cell->getParam(ID(MEMID)).decode_string();
meminit.at(id).push_back(cell->name);
}
}
}
~OptMemWorker()
{
for (auto it : remove_mem)
{
for (auto cell_name : memrd[it])
module->remove(module->cell(cell_name));
for (auto cell_name : memwr[it])
module->remove(module->cell(cell_name));
for (auto cell_name : meminit[it])
module->remove(module->cell(cell_name));
delete module->memories.at(it);
module->memories.erase(it);
}
for (auto cell_name : remove_cells)
module->remove(module->cell(cell_name));
}
int run(RTLIL::Memory *mem)
{
if (restart || remove_mem.count(mem->name))
return 0;
if (memwr.at(mem->name).empty() && meminit.at(mem->name).empty()) {
log("Removing memory %s.%s with no write ports or init data.\n", log_id(module), log_id(mem));
remove_mem.insert(mem->name);
return 1;
}
return 0;
}
};
struct OptMemPass : public Pass {
OptMemPass() : Pass("opt_mem", "optimize memories") { }
void help() YS_OVERRIDE
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" opt_mem [options] [selection]\n");
log("\n");
log("This pass performs various optimizations on memories in the design.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
{
log_header(design, "Executing OPT_MEM pass (optimize memories).\n");
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) {
// if (args[argidx] == "-nomux") {
// mode_nomux = true;
// continue;
// }
break;
}
extra_args(args, argidx, design);
int total_count = 0;
for (auto module : design->selected_modules()) {
while (1) {
int cnt = 0;
OptMemWorker worker(module);
for (auto &it : module->memories)
if (module->selected(it.second))
cnt += worker.run(it.second);
if (!cnt && !worker.restart)
break;
total_count += cnt;
}
}
if (total_count)
design->scratchpad_set_bool("opt.did_something", true);
log("Performed a total of %d transformations.\n", total_count);
}
} OptMemPass;
PRIVATE_NAMESPACE_END

View File

@ -143,13 +143,18 @@ struct WreduceWorker
SigSpec sig_d = mi.sigmap(cell->getPort(ID(D)));
SigSpec sig_q = mi.sigmap(cell->getPort(ID(Q)));
Const initval;
bool is_adff = (cell->type == ID($adff));
Const initval, arst_value;
int width_before = GetSize(sig_q);
if (width_before == 0)
return;
if (cell->parameters.count(ID(ARST_VALUE))) {
arst_value = cell->parameters[ID(ARST_VALUE)];
}
bool zero_ext = sig_d[GetSize(sig_d)-1] == State::S0;
bool sign_ext = !zero_ext;
@ -163,7 +168,8 @@ struct WreduceWorker
for (int i = GetSize(sig_q)-1; i >= 0; i--)
{
if (zero_ext && sig_d[i] == State::S0 && (initval[i] == State::S0 || initval[i] == State::Sx)) {
if (zero_ext && sig_d[i] == State::S0 && (initval[i] == State::S0 || initval[i] == State::Sx) &&
(!is_adff || i >= GetSize(arst_value) || arst_value[i] == State::S0 || arst_value[i] == State::Sx)) {
module->connect(sig_q[i], State::S0);
remove_init_bits.insert(sig_q[i]);
sig_d.remove(i);
@ -171,7 +177,8 @@ struct WreduceWorker
continue;
}
if (sign_ext && i > 0 && sig_d[i] == sig_d[i-1] && initval[i] == initval[i-1]) {
if (sign_ext && i > 0 && sig_d[i] == sig_d[i-1] && initval[i] == initval[i-1] &&
(!is_adff || i >= GetSize(arst_value) || arst_value[i] == arst_value[i-1])) {
module->connect(sig_q[i], sig_q[i-1]);
remove_init_bits.insert(sig_q[i]);
sig_d.remove(i);
@ -214,7 +221,6 @@ struct WreduceWorker
// Narrow ARST_VALUE parameter to new size.
if (cell->parameters.count(ID(ARST_VALUE))) {
Const arst_value = cell->getParam(ID(ARST_VALUE));
arst_value.bits.resize(GetSize(sig_q));
cell->setParam(ID(ARST_VALUE), arst_value);
}

View File

@ -1,5 +1,5 @@
%_pm.h: passes/pmgen/pmgen.py %.pmg
$(P) mkdir -p passes/pmgen && python3 $< -o $@ -p $(subst _pm.h,,$(notdir $@)) $(filter-out $<,$^)
$(P) mkdir -p passes/pmgen && $(PYTHON_EXECUTABLE) $< -o $@ -p $(subst _pm.h,,$(notdir $@)) $(filter-out $<,$^)
# --------------------------------------
@ -21,6 +21,14 @@ $(eval $(call add_extra_objs,passes/pmgen/ice40_wrapcarry_pm.h))
# --------------------------------------
OBJS += passes/pmgen/xilinx_dsp.o
passes/pmgen/xilinx_dsp.o: passes/pmgen/xilinx_dsp_pm.h passes/pmgen/xilinx_dsp_CREG_pm.h passes/pmgen/xilinx_dsp_cascade_pm.h
$(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_pm.h))
$(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_CREG_pm.h))
$(eval $(call add_extra_objs,passes/pmgen/xilinx_dsp_cascade_pm.h))
# --------------------------------------
OBJS += passes/pmgen/peepopt.o
passes/pmgen/peepopt.o: passes/pmgen/peepopt_pm.h
$(eval $(call add_extra_objs,passes/pmgen/peepopt_pm.h))
@ -30,7 +38,7 @@ PEEPOPT_PATTERN += passes/pmgen/peepopt_muldiv.pmg
PEEPOPT_PATTERN += passes/pmgen/peepopt_dffmux.pmg
passes/pmgen/peepopt_pm.h: passes/pmgen/pmgen.py $(PEEPOPT_PATTERN)
$(P) mkdir -p passes/pmgen && python3 $< -o $@ -p peepopt $(filter-out $<,$^)
$(P) mkdir -p passes/pmgen && $(PYTHON_EXECUTABLE) $< -o $@ -p peepopt $(filter-out $<,$^)
# --------------------------------------

140
passes/pmgen/generate.h Normal file
View File

@ -0,0 +1,140 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef PMGEN_GENERATE
#define PMGEN_GENERATE
#define GENERATE_PATTERN(pmclass, pattern) \
generate_pattern<pmclass>([](pmclass &pm, std::function<void()> f){ return pm.run_ ## pattern(f); }, #pmclass, #pattern, design)
void pmtest_addports(Module *module)
{
pool<SigBit> driven_bits, used_bits;
SigMap sigmap(module);
int icnt = 0, ocnt = 0;
for (auto cell : module->cells())
for (auto conn : cell->connections())
{
if (cell->input(conn.first))
for (auto bit : sigmap(conn.second))
used_bits.insert(bit);
if (cell->output(conn.first))
for (auto bit : sigmap(conn.second))
driven_bits.insert(bit);
}
for (auto wire : vector<Wire*>(module->wires()))
{
SigSpec ibits, obits;
for (auto bit : sigmap(wire)) {
if (!used_bits.count(bit))
obits.append(bit);
if (!driven_bits.count(bit))
ibits.append(bit);
}
if (!ibits.empty()) {
Wire *w = module->addWire(stringf("\\i%d", icnt++), GetSize(ibits));
w->port_input = true;
module->connect(ibits, w);
}
if (!obits.empty()) {
Wire *w = module->addWire(stringf("\\o%d", ocnt++), GetSize(obits));
w->port_output = true;
module->connect(w, obits);
}
}
module->fixup_ports();
}
template <class pm>
void generate_pattern(std::function<void(pm&,std::function<void()>)> run, const char *pmclass, const char *pattern, Design *design)
{
log("Generating \"%s\" patterns for pattern matcher \"%s\".\n", pattern, pmclass);
int modcnt = 0;
int maxmodcnt = 100;
int maxsubcnt = 4;
int timeout = 0;
vector<Module*> mods;
while (modcnt < maxmodcnt)
{
int submodcnt = 0, itercnt = 0, cellcnt = 0;
Module *mod = design->addModule(NEW_ID);
while (modcnt < maxmodcnt && submodcnt < maxsubcnt && itercnt++ < 1000)
{
if (timeout++ > 10000)
log_error("pmgen generator is stuck: 10000 iterations with no matching module generated.\n");
pm matcher(mod, mod->cells());
matcher.rng(1);
matcher.rngseed += modcnt;
matcher.rng(1);
matcher.rngseed += submodcnt;
matcher.rng(1);
matcher.rngseed += itercnt;
matcher.rng(1);
matcher.rngseed += cellcnt;
matcher.rng(1);
if (GetSize(mod->cells()) != cellcnt)
{
bool found_match = false;
run(matcher, [&](){ found_match = true; });
cellcnt = GetSize(mod->cells());
if (found_match) {
Module *m = design->addModule(stringf("\\pmtest_%s_%s_%05d",
pmclass, pattern, modcnt++));
log("Creating module %s with %d cells.\n", log_id(m), cellcnt);
mod->cloneInto(m);
pmtest_addports(m);
mods.push_back(m);
submodcnt++;
timeout = 0;
}
}
matcher.generate_mode = true;
run(matcher, [](){});
}
if (submodcnt && maxsubcnt < (1 << 16))
maxsubcnt *= 2;
design->remove(mod);
}
Module *m = design->addModule(stringf("\\pmtest_%s_%s", pmclass, pattern));
log("Creating module %s with %d cells.\n", log_id(m), GetSize(mods));
for (auto mod : mods) {
Cell *c = m->addCell(mod->name, mod->name);
for (auto port : mod->ports) {
Wire *w = m->addWire(NEW_ID, GetSize(mod->wire(port)));
c->setPort(port, w);
}
}
pmtest_addports(m);
}
#endif

View File

@ -29,19 +29,19 @@ void create_ice40_dsp(ice40_dsp_pm &pm)
{
auto &st = pm.st_ice40_dsp;
#if 0
log("\n");
log("ffA: %s\n", log_id(st.ffA, "--"));
log("ffB: %s\n", log_id(st.ffB, "--"));
log("mul: %s\n", log_id(st.mul, "--"));
log("ffY: %s\n", log_id(st.ffY, "--"));
log("addAB: %s\n", log_id(st.addAB, "--"));
log("muxAB: %s\n", log_id(st.muxAB, "--"));
log("ffS: %s\n", log_id(st.ffS, "--"));
#endif
log("Checking %s.%s for iCE40 DSP inference.\n", log_id(pm.module), log_id(st.mul));
log_debug("ffA: %s %s %s\n", log_id(st.ffA, "--"), log_id(st.ffAholdmux, "--"), log_id(st.ffArstmux, "--"));
log_debug("ffB: %s %s %s\n", log_id(st.ffB, "--"), log_id(st.ffBholdmux, "--"), log_id(st.ffBrstmux, "--"));
log_debug("ffCD: %s %s\n", log_id(st.ffCD, "--"), log_id(st.ffCDholdmux, "--"));
log_debug("mul: %s\n", log_id(st.mul, "--"));
log_debug("ffFJKG: %s\n", log_id(st.ffFJKG, "--"));
log_debug("ffH: %s\n", log_id(st.ffH, "--"));
log_debug("add: %s\n", log_id(st.add, "--"));
log_debug("mux: %s\n", log_id(st.mux, "--"));
log_debug("ffO: %s %s %s\n", log_id(st.ffO, "--"), log_id(st.ffOholdmux, "--"), log_id(st.ffOrstmux, "--"));
log_debug("\n");
if (GetSize(st.sigA) > 16) {
log(" input A (%s) is too large (%d > 16).\n", log_signal(st.sigA), GetSize(st.sigA));
return;
@ -52,59 +52,85 @@ void create_ice40_dsp(ice40_dsp_pm &pm)
return;
}
if (GetSize(st.sigS) > 32) {
log(" accumulator (%s) is too large (%d > 32).\n", log_signal(st.sigS), GetSize(st.sigS));
if (GetSize(st.sigO) > 33) {
log(" adder/accumulator (%s) is too large (%d > 33).\n", log_signal(st.sigO), GetSize(st.sigO));
return;
}
if (GetSize(st.sigY) > 32) {
log(" output (%s) is too large (%d > 32).\n", log_signal(st.sigY), GetSize(st.sigY));
if (GetSize(st.sigH) > 32) {
log(" output (%s) is too large (%d > 32).\n", log_signal(st.sigH), GetSize(st.sigH));
return;
}
bool mul_signed = st.mul->getParam("\\A_SIGNED").as_bool();
Cell *cell = st.mul;
if (cell->type == ID($mul)) {
log(" replacing %s with SB_MAC16 cell.\n", log_id(st.mul->type));
log(" replacing $mul with SB_MAC16 cell.\n");
Cell *cell = pm.module->addCell(NEW_ID, "\\SB_MAC16");
cell = pm.module->addCell(NEW_ID, ID(SB_MAC16));
pm.module->swap_names(cell, st.mul);
}
else log_assert(cell->type == ID(SB_MAC16));
// SB_MAC16 Input Interface
SigSpec A = st.sigA;
A.extend_u0(16, mul_signed);
A.extend_u0(16, st.mul->getParam(ID(A_SIGNED)).as_bool());
log_assert(GetSize(A) == 16);
SigSpec B = st.sigB;
B.extend_u0(16, mul_signed);
B.extend_u0(16, st.mul->getParam(ID(B_SIGNED)).as_bool());
log_assert(GetSize(B) == 16);
SigSpec CD;
if (st.muxA)
CD = st.muxA->getPort("\\B");
if (st.muxB)
CD = st.muxB->getPort("\\A");
CD.extend_u0(32, mul_signed);
SigSpec CD = st.sigCD;
if (CD.empty())
CD = RTLIL::Const(0, 32);
else
log_assert(GetSize(CD) == 32);
cell->setPort("\\A", A);
cell->setPort("\\B", B);
cell->setPort("\\C", CD.extract(0, 16));
cell->setPort("\\D", CD.extract(16, 16));
cell->setPort(ID::A, A);
cell->setPort(ID::B, B);
cell->setPort(ID(C), CD.extract(16, 16));
cell->setPort(ID(D), CD.extract(0, 16));
cell->setParam("\\A_REG", st.ffA ? State::S1 : State::S0);
cell->setParam("\\B_REG", st.ffB ? State::S1 : State::S0);
cell->setParam(ID(A_REG), st.ffA ? State::S1 : State::S0);
cell->setParam(ID(B_REG), st.ffB ? State::S1 : State::S0);
cell->setParam(ID(C_REG), st.ffCD ? State::S1 : State::S0);
cell->setParam(ID(D_REG), st.ffCD ? State::S1 : State::S0);
cell->setPort("\\AHOLD", State::S0);
cell->setPort("\\BHOLD", State::S0);
cell->setPort("\\CHOLD", State::S0);
cell->setPort("\\DHOLD", State::S0);
SigSpec AHOLD, BHOLD, CDHOLD;
if (st.ffAholdmux)
AHOLD = st.ffAholdpol ? st.ffAholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffAholdmux->getPort(ID(S)));
else
AHOLD = State::S0;
if (st.ffBholdmux)
BHOLD = st.ffBholdpol ? st.ffBholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffBholdmux->getPort(ID(S)));
else
BHOLD = State::S0;
if (st.ffCDholdmux)
CDHOLD = st.ffCDholdpol ? st.ffCDholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffCDholdmux->getPort(ID(S)));
else
CDHOLD = State::S0;
cell->setPort(ID(AHOLD), AHOLD);
cell->setPort(ID(BHOLD), BHOLD);
cell->setPort(ID(CHOLD), CDHOLD);
cell->setPort(ID(DHOLD), CDHOLD);
cell->setPort("\\IRSTTOP", State::S0);
cell->setPort("\\IRSTBOT", State::S0);
SigSpec IRSTTOP, IRSTBOT;
if (st.ffArstmux)
IRSTTOP = st.ffArstpol ? st.ffArstmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffArstmux->getPort(ID(S)));
else
IRSTTOP = State::S0;
if (st.ffBrstmux)
IRSTBOT = st.ffBrstpol ? st.ffBrstmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffBrstmux->getPort(ID(S)));
else
IRSTBOT = State::S0;
cell->setPort(ID(IRSTTOP), IRSTTOP);
cell->setPort(ID(IRSTBOT), IRSTBOT);
if (st.clock_vld)
if (st.clock != SigBit())
{
cell->setPort("\\CLK", st.clock);
cell->setPort("\\CE", State::S1);
cell->setParam("\\NEG_TRIGGER", st.clock_pol ? State::S0 : State::S1);
cell->setPort(ID(CLK), st.clock);
cell->setPort(ID(CE), State::S1);
cell->setParam(ID(NEG_TRIGGER), st.clock_pol ? State::S0 : State::S1);
log(" clock: %s (%s)", log_signal(st.clock), st.clock_pol ? "posedge" : "negedge");
@ -114,91 +140,137 @@ void create_ice40_dsp(ice40_dsp_pm &pm)
if (st.ffB)
log(" ffB:%s", log_id(st.ffB));
if (st.ffY)
log(" ffY:%s", log_id(st.ffY));
if (st.ffCD)
log(" ffCD:%s", log_id(st.ffCD));
if (st.ffS)
log(" ffS:%s", log_id(st.ffS));
if (st.ffFJKG)
log(" ffFJKG:%s", log_id(st.ffFJKG));
if (st.ffH)
log(" ffH:%s", log_id(st.ffH));
if (st.ffO)
log(" ffO:%s", log_id(st.ffO));
log("\n");
}
else
{
cell->setPort("\\CLK", State::S0);
cell->setPort("\\CE", State::S0);
cell->setParam("\\NEG_TRIGGER", State::S0);
cell->setPort(ID(CLK), State::S0);
cell->setPort(ID(CE), State::S0);
cell->setParam(ID(NEG_TRIGGER), State::S0);
}
// SB_MAC16 Cascade Interface
cell->setPort("\\SIGNEXTIN", State::Sx);
cell->setPort("\\SIGNEXTOUT", pm.module->addWire(NEW_ID));
cell->setPort(ID(SIGNEXTIN), State::Sx);
cell->setPort(ID(SIGNEXTOUT), pm.module->addWire(NEW_ID));
cell->setPort("\\CI", State::Sx);
cell->setPort("\\CO", pm.module->addWire(NEW_ID));
cell->setPort(ID(CI), State::Sx);
cell->setPort("\\ACCUMCI", State::Sx);
cell->setPort("\\ACCUMCO", pm.module->addWire(NEW_ID));
cell->setPort(ID(ACCUMCI), State::Sx);
cell->setPort(ID(ACCUMCO), pm.module->addWire(NEW_ID));
// SB_MAC16 Output Interface
SigSpec O = st.ffS ? st.sigS : st.sigY;
SigSpec O = st.sigO;
int O_width = GetSize(O);
if (O_width == 33) {
log_assert(st.add);
// If we have a signed multiply-add, then perform sign extension
if (st.add->getParam(ID(A_SIGNED)).as_bool() && st.add->getParam(ID(B_SIGNED)).as_bool())
pm.module->connect(O[32], O[31]);
else
cell->setPort(ID(CO), O[32]);
O.remove(O_width-1);
}
else
cell->setPort(ID(CO), pm.module->addWire(NEW_ID));
log_assert(GetSize(O) <= 32);
if (GetSize(O) < 32)
O.append(pm.module->addWire(NEW_ID, 32-GetSize(O)));
cell->setPort("\\O", O);
cell->setPort(ID(O), O);
if (st.addAB) {
log(" accumulator %s (%s)\n", log_id(st.addAB), log_id(st.addAB->type));
cell->setPort("\\ADDSUBTOP", st.addAB->type == "$add" ? State::S0 : State::S1);
cell->setPort("\\ADDSUBBOT", st.addAB->type == "$add" ? State::S0 : State::S1);
bool accum = false;
if (st.add) {
accum = (st.ffO && st.add->getPort(st.addAB == ID::A ? ID::B : ID::A) == st.sigO);
if (accum)
log(" accumulator %s (%s)\n", log_id(st.add), log_id(st.add->type));
else
log(" adder %s (%s)\n", log_id(st.add), log_id(st.add->type));
cell->setPort(ID(ADDSUBTOP), st.add->type == ID($add) ? State::S0 : State::S1);
cell->setPort(ID(ADDSUBBOT), st.add->type == ID($add) ? State::S0 : State::S1);
} else {
cell->setPort("\\ADDSUBTOP", State::S0);
cell->setPort("\\ADDSUBBOT", State::S0);
cell->setPort(ID(ADDSUBTOP), State::S0);
cell->setPort(ID(ADDSUBBOT), State::S0);
}
cell->setPort("\\ORSTTOP", State::S0);
cell->setPort("\\ORSTBOT", State::S0);
SigSpec OHOLD;
if (st.ffOholdmux)
OHOLD = st.ffOholdpol ? st.ffOholdmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffOholdmux->getPort(ID(S)));
else
OHOLD = State::S0;
cell->setPort(ID(OHOLDTOP), OHOLD);
cell->setPort(ID(OHOLDBOT), OHOLD);
cell->setPort("\\OHOLDTOP", State::S0);
cell->setPort("\\OHOLDBOT", State::S0);
SigSpec ORST;
if (st.ffOrstmux)
ORST = st.ffOrstpol ? st.ffOrstmux->getPort(ID(S)) : pm.module->Not(NEW_ID, st.ffOrstmux->getPort(ID(S)));
else
ORST = State::S0;
cell->setPort(ID(ORSTTOP), ORST);
cell->setPort(ID(ORSTBOT), ORST);
SigSpec acc_reset = State::S0;
if (st.muxA)
acc_reset = st.muxA->getPort("\\S");
if (st.muxB)
acc_reset = pm.module->Not(NEW_ID, st.muxB->getPort("\\S"));
cell->setPort("\\OLOADTOP", acc_reset);
cell->setPort("\\OLOADBOT", acc_reset);
if (st.mux) {
if (st.muxAB == ID::A)
acc_reset = st.mux->getPort(ID(S));
else
acc_reset = pm.module->Not(NEW_ID, st.mux->getPort(ID(S)));
}
cell->setPort(ID(OLOADTOP), acc_reset);
cell->setPort(ID(OLOADBOT), acc_reset);
// SB_MAC16 Remaining Parameters
cell->setParam("\\C_REG", State::S0);
cell->setParam("\\D_REG", State::S0);
cell->setParam(ID(TOP_8x8_MULT_REG), st.ffFJKG ? State::S1 : State::S0);
cell->setParam(ID(BOT_8x8_MULT_REG), st.ffFJKG ? State::S1 : State::S0);
cell->setParam(ID(PIPELINE_16x16_MULT_REG1), st.ffFJKG ? State::S1 : State::S0);
cell->setParam(ID(PIPELINE_16x16_MULT_REG2), st.ffH ? State::S1 : State::S0);
cell->setParam("\\TOP_8x8_MULT_REG", st.ffY ? State::S1 : State::S0);
cell->setParam("\\BOT_8x8_MULT_REG", st.ffY ? State::S1 : State::S0);
cell->setParam("\\PIPELINE_16x16_MULT_REG1", st.ffY ? State::S1 : State::S0);
cell->setParam("\\PIPELINE_16x16_MULT_REG2", State::S0);
cell->setParam(ID(TOPADDSUB_LOWERINPUT), Const(2, 2));
cell->setParam(ID(TOPADDSUB_UPPERINPUT), accum ? State::S0 : State::S1);
cell->setParam(ID(TOPADDSUB_CARRYSELECT), Const(3, 2));
cell->setParam("\\TOPOUTPUT_SELECT", Const(st.ffS ? 1 : 3, 2));
cell->setParam("\\TOPADDSUB_LOWERINPUT", Const(2, 2));
cell->setParam("\\TOPADDSUB_UPPERINPUT", State::S0);
cell->setParam("\\TOPADDSUB_CARRYSELECT", Const(3, 2));
cell->setParam(ID(BOTADDSUB_LOWERINPUT), Const(2, 2));
cell->setParam(ID(BOTADDSUB_UPPERINPUT), accum ? State::S0 : State::S1);
cell->setParam(ID(BOTADDSUB_CARRYSELECT), Const(0, 2));
cell->setParam("\\BOTOUTPUT_SELECT", Const(st.ffS ? 1 : 3, 2));
cell->setParam("\\BOTADDSUB_LOWERINPUT", Const(2, 2));
cell->setParam("\\BOTADDSUB_UPPERINPUT", State::S0);
cell->setParam("\\BOTADDSUB_CARRYSELECT", Const(0, 2));
cell->setParam(ID(MODE_8x8), State::S0);
cell->setParam(ID(A_SIGNED), st.mul->getParam(ID(A_SIGNED)).as_bool());
cell->setParam(ID(B_SIGNED), st.mul->getParam(ID(B_SIGNED)).as_bool());
cell->setParam("\\MODE_8x8", State::S0);
cell->setParam("\\A_SIGNED", mul_signed ? State::S1 : State::S0);
cell->setParam("\\B_SIGNED", mul_signed ? State::S1 : State::S0);
if (st.ffO) {
if (st.o_lo)
cell->setParam(ID(TOPOUTPUT_SELECT), Const(st.add ? 0 : 3, 2));
else
cell->setParam(ID(TOPOUTPUT_SELECT), Const(1, 2));
st.ffO->connections_.at(ID(Q)).replace(O, pm.module->addWire(NEW_ID, GetSize(O)));
cell->setParam(ID(BOTOUTPUT_SELECT), Const(1, 2));
}
else {
cell->setParam(ID(TOPOUTPUT_SELECT), Const(st.add ? 0 : 3, 2));
cell->setParam(ID(BOTOUTPUT_SELECT), Const(st.add ? 0 : 3, 2));
}
if (cell != st.mul)
pm.autoremove(st.mul);
pm.autoremove(st.ffY);
pm.autoremove(st.ffS);
else
pm.blacklist(st.mul);
pm.autoremove(st.ffFJKG);
pm.autoremove(st.add);
}
struct Ice40DspPass : public Pass {
@ -209,7 +281,17 @@ struct Ice40DspPass : public Pass {
log("\n");
log(" ice40_dsp [options] [selection]\n");
log("\n");
log("Map multipliers and multiply-accumulate blocks to iCE40 DSP resources.\n");
log("Map multipliers ($mul/SB_MAC16) and multiply-accumulate ($mul/SB_MAC16 + $add)\n");
log("cells into iCE40 DSP resources.\n");
log("Currently, only the 16x16 multiply mode is supported and not the 2 x 8x8 mode.\n");
log("\n");
log("Pack input registers (A, B, {C,D}; with optional hold), pipeline registers\n");
log("({F,J,K,G}, H), output registers (O -- full 32-bits or lower 16-bits only; with\n");
log("optional hold), and post-adder into into the SB_MAC16 resource.\n");
log("\n");
log("Multiply-accumulate operations using the post-adder with feedback on the {C,D}\n");
log("input will be folded into the DSP. In this scenario only, resetting the\n");
log("the accumulator to an arbitrary value can be inferred to use the {C,D} input.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE

View File

@ -1,163 +1,574 @@
pattern ice40_dsp
state <SigBit> clock
state <bool> clock_pol clock_vld
state <SigSpec> sigA sigB sigY sigS
state <Cell*> addAB muxAB
state <bool> clock_pol cd_signed o_lo
state <SigSpec> sigA sigB sigCD sigH sigO
state <Cell*> add mux
state <IdString> addAB muxAB
state <bool> ffAholdpol ffBholdpol ffCDholdpol ffOholdpol
state <bool> ffArstpol ffBrstpol ffCDrstpol ffOrstpol
state <Cell*> ffA ffAholdmux ffArstmux ffB ffBholdmux ffBrstmux ffCD ffCDholdmux
state <Cell*> ffFJKG ffH ffO ffOholdmux ffOrstmux
// subpattern
state <SigSpec> argQ argD
state <bool> ffholdpol ffrstpol
state <int> ffoffset
udata <SigSpec> dffD dffQ
udata <SigBit> dffclock
udata <Cell*> dff dffholdmux dffrstmux
udata <bool> dffholdpol dffrstpol dffclock_pol
match mul
select mul->type.in($mul)
select mul->type.in($mul, \SB_MAC16)
select GetSize(mul->getPort(\A)) + GetSize(mul->getPort(\B)) > 10
select GetSize(mul->getPort(\Y)) > 10
endmatch
match ffA
select ffA->type.in($dff)
// select nusers(port(ffA, \Q)) == 2
index <SigSpec> port(ffA, \Q) === port(mul, \A)
optional
endmatch
code sigA sigB sigH
auto unextend = [](const SigSpec &sig) {
int i;
for (i = GetSize(sig)-1; i > 0; i--)
if (sig[i] != sig[i-1])
break;
// Do not remove non-const sign bit
if (sig[i].wire)
++i;
return sig.extract(0, i);
};
sigA = unextend(port(mul, \A));
sigB = unextend(port(mul, \B));
code sigA clock clock_pol clock_vld
sigA = port(mul, \A);
if (ffA) {
sigA = port(ffA, \D);
clock = port(ffA, \CLK).as_bit();
clock_pol = param(ffA, \CLK_POLARITY).as_bool();
clock_vld = true;
}
endcode
match ffB
select ffB->type.in($dff)
// select nusers(port(ffB, \Q)) == 2
index <SigSpec> port(ffB, \Q) === port(mul, \B)
optional
endmatch
code sigB clock clock_pol clock_vld
sigB = port(mul, \B);
if (ffB) {
sigB = port(ffB, \D);
SigBit c = port(ffB, \CLK).as_bit();
bool cp = param(ffB, \CLK_POLARITY).as_bool();
if (clock_vld && (c != clock || cp != clock_pol))
SigSpec O;
if (mul->type == $mul)
O = mul->getPort(\Y);
else if (mul->type == \SB_MAC16)
O = mul->getPort(\O);
else log_abort();
if (GetSize(O) <= 10)
reject;
clock = c;
clock_pol = cp;
clock_vld = true;
// Only care about those bits that are used
int i;
for (i = 0; i < GetSize(O); i++) {
if (nusers(O[i]) <= 1)
break;
sigH.append(O[i]);
}
log_assert(nusers(O.extract_end(i)) <= 1);
endcode
code argQ ffA ffAholdmux ffArstmux ffAholdpol ffArstpol sigA clock clock_pol
if (mul->type != \SB_MAC16 || !param(mul, \A_REG).as_bool()) {
argQ = sigA;
subpattern(in_dffe);
if (dff) {
ffA = dff;
clock = dffclock;
clock_pol = dffclock_pol;
if (dffrstmux) {
ffArstmux = dffrstmux;
ffArstpol = dffrstpol;
}
if (dffholdmux) {
ffAholdmux = dffholdmux;
ffAholdpol = dffholdpol;
}
sigA = dffD;
}
}
endcode
match ffY
select ffY->type.in($dff)
select nusers(port(ffY, \D)) == 2
index <SigSpec> port(ffY, \D) === port(mul, \Y)
optional
endmatch
code sigY clock clock_pol clock_vld
sigY = port(mul, \Y);
if (ffY) {
sigY = port(ffY, \Q);
SigBit c = port(ffY, \CLK).as_bit();
bool cp = param(ffY, \CLK_POLARITY).as_bool();
if (clock_vld && (c != clock || cp != clock_pol))
reject;
clock = c;
clock_pol = cp;
clock_vld = true;
code argQ ffB ffBholdmux ffBrstmux ffBholdpol ffBrstpol sigB clock clock_pol
if (mul->type != \SB_MAC16 || !param(mul, \B_REG).as_bool()) {
argQ = sigB;
subpattern(in_dffe);
if (dff) {
ffB = dff;
clock = dffclock;
clock_pol = dffclock_pol;
if (dffrstmux) {
ffBrstmux = dffrstmux;
ffBrstpol = dffrstpol;
}
if (dffholdmux) {
ffBholdmux = dffholdmux;
ffBholdpol = dffholdpol;
}
sigB = dffD;
}
}
endcode
match addA
select addA->type.in($add)
select nusers(port(addA, \A)) == 2
index <SigSpec> port(addA, \A) === sigY
code argD ffFJKG sigH clock clock_pol
if (nusers(sigH) == 2 &&
(mul->type != \SB_MAC16 ||
(!param(mul, \TOP_8x8_MULT_REG).as_bool() && !param(mul, \BOT_8x8_MULT_REG).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG1).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG1).as_bool()))) {
argD = sigH;
subpattern(out_dffe);
if (dff) {
// F/J/K/G do not have a CE-like (hold) input
if (dffholdmux)
goto reject_ffFJKG;
// Reset signal of F/J (IRSTTOP) and K/G (IRSTBOT)
// shared with A and B
if ((ffArstmux != NULL) != (dffrstmux != NULL))
goto reject_ffFJKG;
if ((ffBrstmux != NULL) != (dffrstmux != NULL))
goto reject_ffFJKG;
if (ffArstmux) {
if (port(ffArstmux, \S) != port(dffrstmux, \S))
goto reject_ffFJKG;
if (ffArstpol != dffrstpol)
goto reject_ffFJKG;
}
if (ffBrstmux) {
if (port(ffBrstmux, \S) != port(dffrstmux, \S))
goto reject_ffFJKG;
if (ffBrstpol != dffrstpol)
goto reject_ffFJKG;
}
ffFJKG = dff;
clock = dffclock;
clock_pol = dffclock_pol;
sigH = dffQ;
reject_ffFJKG: ;
}
}
endcode
code argD ffH sigH sigO clock clock_pol
if (ffFJKG && nusers(sigH) == 2 &&
(mul->type != \SB_MAC16 || !param(mul, \PIPELINE_16x16_MULT_REG2).as_bool())) {
argD = sigH;
subpattern(out_dffe);
if (dff) {
// H does not have a CE-like (hold) input
if (dffholdmux)
goto reject_ffH;
// Reset signal of H (IRSTBOT) shared with B
if ((ffBrstmux != NULL) != (dffrstmux != NULL))
goto reject_ffH;
if (ffBrstmux) {
if (port(ffBrstmux, \S) != port(dffrstmux, \S))
goto reject_ffH;
if (ffBrstpol != dffrstpol)
goto reject_ffH;
}
ffH = dff;
clock = dffclock;
clock_pol = dffclock_pol;
sigH = dffQ;
reject_ffH: ;
}
}
sigO = sigH;
endcode
match add
if mul->type != \SB_MAC16 || (param(mul, \TOPOUTPUT_SELECT).as_int() == 3 && param(mul, \BOTOUTPUT_SELECT).as_int() == 3)
select add->type.in($add)
choice <IdString> AB {\A, \B}
select nusers(port(add, AB)) == 2
index <SigBit> port(add, AB)[0] === sigH[0]
filter GetSize(port(add, AB)) <= GetSize(sigH)
filter port(add, AB) == sigH.extract(0, GetSize(port(add, AB)))
filter nusers(sigH.extract_end(GetSize(port(add, AB)))) <= 1
set addAB AB
optional
endmatch
match addB
if !addA
select addB->type.in($add, $sub)
select nusers(port(addB, \B)) == 2
index <SigSpec> port(addB, \B) === sigY
optional
endmatch
code sigCD sigO cd_signed
if (add) {
sigCD = port(add, addAB == \A ? \B : \A);
cd_signed = param(add, addAB == \A ? \B_SIGNED : \A_SIGNED).as_bool();
code addAB sigS
if (addA) {
addAB = addA;
sigS = port(addA, \B);
}
if (addB) {
addAB = addB;
sigS = port(addB, \A);
}
if (addAB) {
int natural_mul_width = GetSize(sigA) + GetSize(sigB);
int actual_mul_width = GetSize(sigY);
int actual_acc_width = GetSize(sigS);
int actual_mul_width = GetSize(sigH);
int actual_acc_width = GetSize(sigCD);
if ((actual_acc_width > actual_mul_width) && (natural_mul_width > actual_mul_width))
reject;
if ((actual_acc_width != actual_mul_width) && (param(mul, \A_SIGNED).as_bool() != param(addAB, \A_SIGNED).as_bool()))
// If accumulator, check adder width and signedness
if (sigCD == sigH && (actual_acc_width != actual_mul_width) && (param(mul, \A_SIGNED).as_bool() != param(add, \A_SIGNED).as_bool()))
reject;
sigO = port(add, \Y);
}
endcode
match muxA
if addAB
select muxA->type.in($mux)
select nusers(port(muxA, \A)) == 2
index <SigSpec> port(muxA, \A) === port(addAB, \Y)
match mux
select mux->type == $mux
choice <IdString> AB {\A, \B}
select nusers(port(mux, AB)) == 2
index <SigSpec> port(mux, AB) === sigO
set muxAB AB
optional
endmatch
match muxB
if addAB
if !muxA
select muxB->type.in($mux)
select nusers(port(muxB, \B)) == 2
index <SigSpec> port(muxB, \B) === port(addAB, \Y)
optional
endmatch
code muxAB
muxAB = addAB;
if (muxA)
muxAB = muxA;
if (muxB)
muxAB = muxB;
code sigO
if (mux)
sigO = port(mux, \Y);
endcode
match ffS
if muxAB
select ffS->type.in($dff)
select nusers(port(ffS, \D)) == 2
index <SigSpec> port(ffS, \D) === port(muxAB, \Y)
index <SigSpec> port(ffS, \Q) === sigS
endmatch
code argD ffO ffOholdmux ffOrstmux ffOholdpol ffOrstpol sigO sigCD clock clock_pol cd_signed o_lo
if (mul->type != \SB_MAC16 ||
// Ensure that register is not already used
((param(mul, \TOPOUTPUT_SELECT, 0).as_int() != 1 && param(mul, \BOTOUTPUT_SELECT, 0).as_int() != 1) &&
// Ensure that OLOADTOP/OLOADBOT is unused or zero
(port(mul, \OLOADTOP, State::S0).is_fully_zero() && port(mul, \OLOADBOT, State::S0).is_fully_zero()))) {
code clock clock_pol clock_vld
if (ffS) {
SigBit c = port(ffS, \CLK).as_bit();
bool cp = param(ffS, \CLK_POLARITY).as_bool();
dff = nullptr;
if (clock_vld && (c != clock || cp != clock_pol))
reject;
clock = c;
clock_pol = cp;
clock_vld = true;
// First try entire sigO
if (nusers(sigO) == 2) {
argD = sigO;
subpattern(out_dffe);
}
// Otherwise try just its least significant 16 bits
if (!dff && GetSize(sigO) > 16) {
argD = sigO.extract(0, 16);
if (nusers(argD) == 2) {
subpattern(out_dffe);
o_lo = dff;
}
}
if (dff) {
ffO = dff;
clock = dffclock;
clock_pol = dffclock_pol;
if (dffrstmux) {
ffOrstmux = dffrstmux;
ffOrstpol = dffrstpol;
}
if (dffholdmux) {
ffOholdmux = dffholdmux;
ffOholdpol = dffholdpol;
}
sigO.replace(sigO.extract(0, GetSize(dffQ)), dffQ);
}
// Loading value into output register is not
// supported unless using accumulator
if (mux) {
if (sigCD != sigO)
reject;
sigCD = port(mux, muxAB == \B ? \A : \B);
cd_signed = add && param(add, \A_SIGNED).as_bool() && param(add, \B_SIGNED).as_bool();
}
}
endcode
code argQ ffCD ffCDholdmux ffCDholdpol ffCDrstpol sigCD clock clock_pol
if (!sigCD.empty() && sigCD != sigO &&
(mul->type != \SB_MAC16 || (!param(mul, \C_REG).as_bool() && !param(mul, \D_REG).as_bool()))) {
argQ = sigCD;
subpattern(in_dffe);
if (dff) {
if (dffholdmux) {
ffCDholdmux = dffholdmux;
ffCDholdpol = dffholdpol;
}
// Reset signal of C (IRSTTOP) and D (IRSTBOT)
// shared with A and B
if ((ffArstmux != NULL) != (dffrstmux != NULL))
goto reject_ffCD;
if ((ffBrstmux != NULL) != (dffrstmux != NULL))
goto reject_ffCD;
if (ffArstmux) {
if (port(ffArstmux, \S) != port(dffrstmux, \S))
goto reject_ffCD;
if (ffArstpol != dffrstpol)
goto reject_ffCD;
}
if (ffBrstmux) {
if (port(ffBrstmux, \S) != port(dffrstmux, \S))
goto reject_ffCD;
if (ffBrstpol != dffrstpol)
goto reject_ffCD;
}
ffCD = dff;
clock = dffclock;
clock_pol = dffclock_pol;
sigCD = dffD;
reject_ffCD: ;
}
}
endcode
code sigCD
sigCD.extend_u0(32, cd_signed);
endcode
code
accept;
endcode
// #######################
subpattern in_dffe
arg argD argQ clock clock_pol
code
dff = nullptr;
for (auto c : argQ.chunks()) {
if (!c.wire)
reject;
if (c.wire->get_bool_attribute(\keep))
reject;
Const init = c.wire->attributes.at(\init, State::Sx);
if (!init.is_fully_undef() && !init.is_fully_zero())
reject;
}
endcode
match ff
select ff->type.in($dff)
// DSP48E1 does not support clock inversion
select param(ff, \CLK_POLARITY).as_bool()
slice offset GetSize(port(ff, \D))
index <SigBit> port(ff, \Q)[offset] === argQ[0]
// Check that the rest of argQ is present
filter GetSize(port(ff, \Q)) >= offset + GetSize(argQ)
filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
set ffoffset offset
endmatch
code argQ argD
{
if (clock != SigBit()) {
if (port(ff, \CLK) != clock)
reject;
if (param(ff, \CLK_POLARITY).as_bool() != clock_pol)
reject;
}
SigSpec Q = port(ff, \Q);
dff = ff;
dffclock = port(ff, \CLK);
dffclock_pol = param(ff, \CLK_POLARITY).as_bool();
dffD = argQ;
argD = port(ff, \D);
argQ = Q;
dffD.replace(argQ, argD);
// Only search for ffrstmux if dffD only
// has two (ff, ffrstmux) users
if (nusers(dffD) > 2)
argD = SigSpec();
}
endcode
match ffrstmux
if false /* TODO: ice40 resets are actually async */
if !argD.empty()
select ffrstmux->type.in($mux)
index <SigSpec> port(ffrstmux, \Y) === argD
choice <IdString> BA {\B, \A}
// DSP48E1 only supports reset to zero
select port(ffrstmux, BA).is_fully_zero()
define <bool> pol (BA == \B)
set ffrstpol pol
semioptional
endmatch
code argD
if (ffrstmux) {
dffrstmux = ffrstmux;
dffrstpol = ffrstpol;
argD = port(ffrstmux, ffrstpol ? \A : \B);
dffD.replace(port(ffrstmux, \Y), argD);
// Only search for ffholdmux if argQ has at
// least 3 users (ff, <upstream>, ffrstmux) and
// dffD only has two (ff, ffrstmux)
if (!(nusers(argQ) >= 3 && nusers(dffD) == 2))
argD = SigSpec();
}
else
dffrstmux = nullptr;
endcode
match ffholdmux
if !argD.empty()
select ffholdmux->type.in($mux)
index <SigSpec> port(ffholdmux, \Y) === argD
choice <IdString> BA {\B, \A}
index <SigSpec> port(ffholdmux, BA) === argQ
define <bool> pol (BA == \B)
set ffholdpol pol
semioptional
endmatch
code argD
if (ffholdmux) {
dffholdmux = ffholdmux;
dffholdpol = ffholdpol;
argD = port(ffholdmux, ffholdpol ? \A : \B);
dffD.replace(port(ffholdmux, \Y), argD);
}
else
dffholdmux = nullptr;
endcode
// #######################
subpattern out_dffe
arg argD argQ clock clock_pol
code
dff = nullptr;
for (auto c : argD.chunks())
if (c.wire->get_bool_attribute(\keep))
reject;
endcode
match ffholdmux
select ffholdmux->type.in($mux)
// ffholdmux output must have two users: ffholdmux and ff.D
select nusers(port(ffholdmux, \Y)) == 2
choice <IdString> BA {\B, \A}
// keep-last-value net must have at least three users: ffholdmux, ff, downstream sink(s)
select nusers(port(ffholdmux, BA)) >= 3
slice offset GetSize(port(ffholdmux, \Y))
define <IdString> AB (BA == \B ? \A : \B)
index <SigBit> port(ffholdmux, AB)[offset] === argD[0]
// Check that the rest of argD is present
filter GetSize(port(ffholdmux, AB)) >= offset + GetSize(argD)
filter port(ffholdmux, AB).extract(offset, GetSize(argD)) == argD
set ffoffset offset
define <bool> pol (BA == \B)
set ffholdpol pol
semioptional
endmatch
code argD argQ
dffholdmux = ffholdmux;
if (ffholdmux) {
SigSpec AB = port(ffholdmux, ffholdpol ? \A : \B);
SigSpec Y = port(ffholdmux, \Y);
argQ = argD;
argD.replace(AB, Y);
argQ.replace(AB, port(ffholdmux, ffholdpol ? \B : \A));
dffholdmux = ffholdmux;
dffholdpol = ffholdpol;
}
endcode
match ffrstmux
if false /* TODO: ice40 resets are actually async */
select ffrstmux->type.in($mux)
// ffrstmux output must have two users: ffrstmux and ff.D
select nusers(port(ffrstmux, \Y)) == 2
choice <IdString> BA {\B, \A}
// DSP48E1 only supports reset to zero
select port(ffrstmux, BA).is_fully_zero()
slice offset GetSize(port(ffrstmux, \Y))
define <IdString> AB (BA == \B ? \A : \B)
index <SigBit> port(ffrstmux, AB)[offset] === argD[0]
// Check that offset is consistent
filter !ffholdmux || ffoffset == offset
// Check that the rest of argD is present
filter GetSize(port(ffrstmux, AB)) >= offset + GetSize(argD)
filter port(ffrstmux, AB).extract(offset, GetSize(argD)) == argD
set ffoffset offset
define <bool> pol (AB == \A)
set ffrstpol pol
semioptional
endmatch
code argD argQ
dffrstmux = ffrstmux;
if (ffrstmux) {
SigSpec AB = port(ffrstmux, ffrstpol ? \A : \B);
SigSpec Y = port(ffrstmux, \Y);
argD.replace(AB, Y);
dffrstmux = ffrstmux;
dffrstpol = ffrstpol;
}
endcode
match ff
select ff->type.in($dff)
// DSP48E1 does not support clock inversion
select param(ff, \CLK_POLARITY).as_bool()
slice offset GetSize(port(ff, \D))
index <SigBit> port(ff, \D)[offset] === argD[0]
// Check that offset is consistent
filter (!ffholdmux && !ffrstmux) || ffoffset == offset
// Check that the rest of argD is present
filter GetSize(port(ff, \D)) >= offset + GetSize(argD)
filter port(ff, \D).extract(offset, GetSize(argD)) == argD
// Check that FF.Q is connected to CE-mux
filter !ffholdmux || port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
set ffoffset offset
endmatch
code argQ
if (ff) {
if (clock != SigBit()) {
if (port(ff, \CLK) != clock)
reject;
if (param(ff, \CLK_POLARITY).as_bool() != clock_pol)
reject;
}
SigSpec D = port(ff, \D);
SigSpec Q = port(ff, \Q);
if (!ffholdmux) {
argQ = argD;
argQ.replace(D, Q);
}
for (auto c : argQ.chunks()) {
Const init = c.wire->attributes.at(\init, State::Sx);
if (!init.is_fully_undef() && !init.is_fully_zero())
reject;
}
dff = ff;
dffQ = argQ;
dffclock = port(ff, \CLK);
dffclock_pol = param(ff, \CLK_POLARITY).as_bool();
}
// No enable/reset mux possible without flop
else if (dffholdmux || dffrstmux)
reject;
endcode

View File

@ -9,3 +9,7 @@ match lut
index <SigSpec> port(lut, \I1) === port(carry, \I0)
index <SigSpec> port(lut, \I2) === port(carry, \I1)
endmatch
code
accept;
endcode

View File

@ -24,8 +24,11 @@ USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
bool did_something;
dict<SigBit, State> initbits;
pool<SigBit> rminitbits;
#include "passes/pmgen/peepopt_pm.h"
#include "generate.h"
struct PeepoptPass : public Pass {
PeepoptPass() : Pass("peepopt", "collection of peephole optimizers") { }
@ -40,27 +43,86 @@ struct PeepoptPass : public Pass {
}
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
{
std::string genmode;
log_header(design, "Executing PEEPOPT pass (run peephole optimizers).\n");
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
{
// if (args[argidx] == "-singleton") {
// singleton_mode = true;
// continue;
// }
if (args[argidx] == "-generate" && argidx+1 < args.size()) {
genmode = args[++argidx];
continue;
}
break;
}
extra_args(args, argidx, design);
for (auto module : design->selected_modules()) {
if (!genmode.empty())
{
initbits.clear();
rminitbits.clear();
if (genmode == "shiftmul")
GENERATE_PATTERN(peepopt_pm, shiftmul);
else if (genmode == "muldiv")
GENERATE_PATTERN(peepopt_pm, muldiv);
else if (genmode == "dffmux")
GENERATE_PATTERN(peepopt_pm, dffmux);
else
log_abort();
return;
}
for (auto module : design->selected_modules())
{
did_something = true;
while (did_something) {
while (did_something)
{
did_something = false;
peepopt_pm pm(module, module->selected_cells());
initbits.clear();
rminitbits.clear();
peepopt_pm pm(module);
for (auto w : module->wires()) {
auto it = w->attributes.find(ID(init));
if (it != w->attributes.end()) {
SigSpec sig = pm.sigmap(w);
Const val = it->second;
int len = std::min(GetSize(sig), GetSize(val));
for (int i = 0; i < len; i++) {
if (sig[i].wire == nullptr)
continue;
if (val[i] != State::S0 && val[i] != State::S1)
continue;
initbits[sig[i]] = val[i];
}
}
}
pm.setup(module->selected_cells());
pm.run_shiftmul();
pm.run_muldiv();
pm.run_dffmux();
for (auto w : module->wires()) {
auto it = w->attributes.find(ID(init));
if (it != w->attributes.end()) {
SigSpec sig = pm.sigmap(w);
Const &val = it->second;
int len = std::min(GetSize(sig), GetSize(val));
for (int i = 0; i < len; i++) {
if (rminitbits.count(sig[i]))
val[i] = State::Sx;
}
}
}
initbits.clear();
rminitbits.clear();
}
}
}

View File

@ -8,21 +8,23 @@ match dff
select GetSize(port(dff, \D)) > 1
endmatch
code sigD
sigD = port(dff, \D);
endcode
match rstmux
select rstmux->type == $mux
select GetSize(port(rstmux, \Y)) > 1
index <SigSpec> port(rstmux, \Y) === port(dff, \D)
index <SigSpec> port(rstmux, \Y) === sigD
choice <IdString> BA {\B, \A}
select port(rstmux, BA).is_fully_const()
set rstmuxBA BA
optional
semioptional
endmatch
code sigD
if (rstmux)
sigD = port(rstmux, rstmuxBA == \B ? \A : \B);
else
sigD = port(dff, \D);
endcode
match cemux
@ -32,67 +34,111 @@ match cemux
choice <IdString> AB {\A, \B}
index <SigSpec> port(cemux, AB) === port(dff, \Q)
set cemuxAB AB
semioptional
endmatch
code
SigSpec D = port(cemux, cemuxAB == \A ? \B : \A);
SigSpec Q = port(dff, \Q);
if (!cemux && !rstmux)
reject;
endcode
code
Const rst;
SigSpec D;
if (cemux) {
D = port(cemux, cemuxAB == \A ? \B : \A);
if (rstmux)
rst = port(rstmux, rstmuxBA).as_const();
else
rst = Const(State::Sx, GetSize(D));
}
else {
log_assert(rstmux);
D = port(rstmux, rstmuxBA == \B ? \A : \B);
rst = port(rstmux, rstmuxBA).as_const();
}
SigSpec Q = port(dff, \Q);
int width = GetSize(D);
SigSpec &ceA = cemux->connections_.at(\A);
SigSpec &ceB = cemux->connections_.at(\B);
SigSpec &ceY = cemux->connections_.at(\Y);
SigSpec &dffD = dff->connections_.at(\D);
SigSpec &dffQ = dff->connections_.at(\Q);
SigSpec dffD = dff->getPort(\D);
SigSpec dffQ = dff->getPort(\Q);
if (D[width-1] == D[width-2]) {
did_something = true;
SigBit sign = D[width-1];
bool is_signed = sign.wire;
int i;
for (i = width-1; i >= 2; i--) {
if (!is_signed) {
module->connect(Q[i], sign);
if (D[i-1] != sign || (rst.size() && rst[i-1] != rst[width-1]))
break;
Const initval;
for (auto b : Q) {
auto it = initbits.find(b);
initval.bits.push_back(it == initbits.end() ? State::Sx : it->second);
}
else {
auto cmpx = [=](State lhs, State rhs) {
if (lhs == State::Sx || rhs == State::Sx)
return true;
return lhs == rhs;
};
int i = width-1;
while (i > 1) {
if (D[i] != D[i-1])
break;
if (!cmpx(rst[i], rst[i-1]))
break;
if (!cmpx(initval[i], initval[i-1]))
break;
if (!cmpx(rst[i], initval[i]))
break;
rminitbits.insert(Q[i]);
module->connect(Q[i], Q[i-1]);
if (D[i-2] != sign || (rst.size() && rst[i-1] != rst[width-1]))
break;
i--;
}
}
ceA.remove(i, width-i);
ceB.remove(i, width-i);
ceY.remove(i, width-i);
if (i < width-1) {
did_something = true;
if (cemux) {
SigSpec ceA = cemux->getPort(\A);
SigSpec ceB = cemux->getPort(\B);
SigSpec ceY = cemux->getPort(\Y);
ceA.remove(i, width-1-i);
ceB.remove(i, width-1-i);
ceY.remove(i, width-1-i);
cemux->setPort(\A, ceA);
cemux->setPort(\B, ceB);
cemux->setPort(\Y, ceY);
cemux->fixup_parameters();
dffD.remove(i, width-i);
dffQ.remove(i, width-i);
dff->fixup_parameters();
log("dffcemux pattern in %s: dff=%s, cemux=%s; removed top %d bits.\n", log_id(module), log_id(dff), log_id(cemux), width-i);
accept;
blacklist(cemux);
}
else {
if (rstmux) {
SigSpec rstA = rstmux->getPort(\A);
SigSpec rstB = rstmux->getPort(\B);
SigSpec rstY = rstmux->getPort(\Y);
rstA.remove(i, width-1-i);
rstB.remove(i, width-1-i);
rstY.remove(i, width-1-i);
rstmux->setPort(\A, rstA);
rstmux->setPort(\B, rstB);
rstmux->setPort(\Y, rstY);
rstmux->fixup_parameters();
blacklist(rstmux);
}
dffD.remove(i, width-1-i);
dffQ.remove(i, width-1-i);
dff->setPort(\D, dffD);
dff->setPort(\Q, dffQ);
dff->fixup_parameters();
blacklist(dff);
log("dffcemux pattern in %s: dff=%s, cemux=%s, rstmux=%s; removed top %d bits.\n", log_id(module), log_id(dff), log_id(cemux, "n/a"), log_id(rstmux, "n/a"), width-1-i);
width = i+1;
}
if (cemux) {
SigSpec ceA = cemux->getPort(\A);
SigSpec ceB = cemux->getPort(\B);
SigSpec ceY = cemux->getPort(\Y);
int count = 0;
for (int i = width-1; i >= 0; i--) {
if (D[i].wire)
continue;
Wire *w = Q[i].wire;
auto it = w->attributes.find(\init);
State init;
if (it != w->attributes.end())
init = it->second[Q[i].offset];
else
init = State::Sx;
if (init == State::Sx || init == D[i].data) {
if (cmpx(rst[i], D[i].data) && cmpx(initval[i], D[i].data)) {
count++;
rminitbits.insert(Q[i]);
module->connect(Q[i], D[i]);
ceA.remove(i);
ceB.remove(i);
@ -101,13 +147,25 @@ code
dffQ.remove(i);
}
}
if (count > 0) {
if (count > 0)
{
did_something = true;
cemux->setPort(\A, ceA);
cemux->setPort(\B, ceB);
cemux->setPort(\Y, ceY);
cemux->fixup_parameters();
blacklist(cemux);
dff->setPort(\D, dffD);
dff->setPort(\Q, dffQ);
dff->fixup_parameters();
log("dffcemux pattern in %s: dff=%s, cemux=%s; removed %d constant bits.\n", log_id(module), log_id(dff), log_id(cemux), count);
blacklist(dff);
log("dffcemux pattern in %s: dff=%s, cemux=%s, rstmux=%s; removed %d constant bits.\n", log_id(module), log_id(dff), log_id(cemux), log_id(rstmux, "n/a"), count);
}
}
if (did_something)
accept;
}
endcode

View File

@ -286,7 +286,7 @@ def process_pmgfile(f, filename):
block["gencode"].append(rewrite_cpp(l.rstrip()))
break
assert False
raise RuntimeError("'%s' statement not recognised on line %d" % (a[0], linenr))
if block["optional"]:
assert not block["semioptional"]
@ -305,7 +305,8 @@ def process_pmgfile(f, filename):
block["states"] = set()
for s in line.split()[1:]:
assert s in state_types[current_pattern]
if s not in state_types[current_pattern]:
raise RuntimeError("'%s' not in state_types" % s)
block["states"].add(s)
codetype = "code"
@ -327,7 +328,7 @@ def process_pmgfile(f, filename):
blocks.append(block)
continue
assert False
raise RuntimeError("'%s' command not recognised" % cmd)
for fn in pmgfiles:
with open(fn, "r") as f:
@ -361,6 +362,7 @@ with open(outfile, "w") as f:
print(" Module *module;", file=f)
print(" SigMap sigmap;", file=f)
print(" std::function<void()> on_accept;", file=f)
print(" bool setup_done;", file=f)
print(" bool generate_mode;", file=f)
print(" int accept_cnt;", file=f)
print("", file=f)
@ -452,11 +454,19 @@ with open(outfile, "w") as f:
print(" return sigmap(cell->getPort(portname));", file=f)
print(" }", file=f)
print("", file=f)
print(" SigSpec port(Cell *cell, IdString portname, const SigSpec& defval) {", file=f)
print(" return sigmap(cell->connections_.at(portname, defval));", file=f)
print(" }", file=f)
print("", file=f)
print(" Const param(Cell *cell, IdString paramname) {", file=f)
print(" return cell->getParam(paramname);", file=f)
print(" }", file=f)
print("", file=f)
print(" Const param(Cell *cell, IdString paramname, const Const& defval) {", file=f)
print(" return cell->parameters.at(paramname, defval);", file=f)
print(" }", file=f)
print("", file=f)
print(" int nusers(const SigSpec &sig) {", file=f)
print(" pool<Cell*> users;", file=f)
@ -468,7 +478,17 @@ with open(outfile, "w") as f:
print("", file=f)
print(" {}_pm(Module *module, const vector<Cell*> &cells) :".format(prefix), file=f)
print(" module(module), sigmap(module), generate_mode(false), rngseed(12345678) {", file=f)
print(" module(module), sigmap(module), setup_done(false), generate_mode(false), rngseed(12345678) {", file=f)
print(" setup(cells);", file=f)
print(" }", file=f)
print("", file=f)
print(" {}_pm(Module *module) :".format(prefix), file=f)
print(" module(module), sigmap(module), setup_done(false), generate_mode(false), rngseed(12345678) {", file=f)
print(" }", file=f)
print("", file=f)
print(" void setup(const vector<Cell*> &cells) {", file=f)
for current_pattern in sorted(patterns.keys()):
for s, t in sorted(udata_types[current_pattern].items()):
if t.endswith("*"):
@ -476,6 +496,8 @@ with open(outfile, "w") as f:
else:
print(" ud_{}.{} = {}();".format(current_pattern, s, t), file=f)
current_pattern = None
print(" log_assert(!setup_done);", file=f)
print(" setup_done = true;", file=f)
print(" for (auto port : module->ports)", file=f)
print(" add_siguser(module->wire(port), nullptr);", file=f)
print(" for (auto cell : module->cells())", file=f)
@ -530,6 +552,7 @@ with open(outfile, "w") as f:
for current_pattern in sorted(patterns.keys()):
print(" int run_{}(std::function<void()> on_accept_f) {{".format(current_pattern), file=f)
print(" log_assert(setup_done);", file=f)
print(" accept_cnt = 0;", file=f)
print(" on_accept = on_accept_f;", file=f)
print(" rollback = 0;", file=f)

View File

@ -23,13 +23,11 @@
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
// for peepopt_pm
bool did_something;
#include "passes/pmgen/test_pmgen_pm.h"
#include "passes/pmgen/ice40_dsp_pm.h"
#include "passes/pmgen/xilinx_srl_pm.h"
#include "passes/pmgen/peepopt_pm.h"
#include "generate.h"
void reduce_chain(test_pmgen_pm &pm)
{
@ -118,123 +116,6 @@ void opt_eqpmux(test_pmgen_pm &pm)
log(" -> %s (%s)\n", log_id(c), log_id(c->type));
}
#define GENERATE_PATTERN(pmclass, pattern) \
generate_pattern<pmclass>([](pmclass &pm, std::function<void()> f){ return pm.run_ ## pattern(f); }, #pmclass, #pattern, design)
void pmtest_addports(Module *module)
{
pool<SigBit> driven_bits, used_bits;
SigMap sigmap(module);
int icnt = 0, ocnt = 0;
for (auto cell : module->cells())
for (auto conn : cell->connections())
{
if (cell->input(conn.first))
for (auto bit : sigmap(conn.second))
used_bits.insert(bit);
if (cell->output(conn.first))
for (auto bit : sigmap(conn.second))
driven_bits.insert(bit);
}
for (auto wire : vector<Wire*>(module->wires()))
{
SigSpec ibits, obits;
for (auto bit : sigmap(wire)) {
if (!used_bits.count(bit))
obits.append(bit);
if (!driven_bits.count(bit))
ibits.append(bit);
}
if (!ibits.empty()) {
Wire *w = module->addWire(stringf("\\i%d", icnt++), GetSize(ibits));
w->port_input = true;
module->connect(ibits, w);
}
if (!obits.empty()) {
Wire *w = module->addWire(stringf("\\o%d", ocnt++), GetSize(obits));
w->port_output = true;
module->connect(w, obits);
}
}
module->fixup_ports();
}
template <class pm>
void generate_pattern(std::function<void(pm&,std::function<void()>)> run, const char *pmclass, const char *pattern, Design *design)
{
log("Generating \"%s\" patterns for pattern matcher \"%s\".\n", pattern, pmclass);
int modcnt = 0;
int maxmodcnt = 100;
int maxsubcnt = 4;
int timeout = 0;
vector<Module*> mods;
while (modcnt < maxmodcnt)
{
int submodcnt = 0, itercnt = 0, cellcnt = 0;
Module *mod = design->addModule(NEW_ID);
while (modcnt < maxmodcnt && submodcnt < maxsubcnt && itercnt++ < 1000)
{
if (timeout++ > 10000)
log_error("pmgen generator is stuck: 10000 iterations with no matching module generated.\n");
pm matcher(mod, mod->cells());
matcher.rng(1);
matcher.rngseed += modcnt;
matcher.rng(1);
matcher.rngseed += submodcnt;
matcher.rng(1);
matcher.rngseed += itercnt;
matcher.rng(1);
matcher.rngseed += cellcnt;
matcher.rng(1);
if (GetSize(mod->cells()) != cellcnt)
{
bool found_match = false;
run(matcher, [&](){ found_match = true; });
cellcnt = GetSize(mod->cells());
if (found_match) {
Module *m = design->addModule(stringf("\\pmtest_%s_%s_%05d",
pmclass, pattern, modcnt++));
log("Creating module %s with %d cells.\n", log_id(m), cellcnt);
mod->cloneInto(m);
pmtest_addports(m);
mods.push_back(m);
submodcnt++;
timeout = 0;
}
}
matcher.generate_mode = true;
run(matcher, [](){});
}
if (submodcnt && maxsubcnt < (1 << 16))
maxsubcnt *= 2;
design->remove(mod);
}
Module *m = design->addModule(stringf("\\pmtest_%s_%s", pmclass, pattern));
log("Creating module %s with %d cells.\n", log_id(m), GetSize(mods));
for (auto mod : mods) {
Cell *c = m->addCell(mod->name, mod->name);
for (auto port : mod->ports) {
Wire *w = m->addWire(NEW_ID, GetSize(mod->wire(port)));
c->setPort(port, w);
}
}
pmtest_addports(m);
}
struct TestPmgenPass : public Pass {
TestPmgenPass() : Pass("test_pmgen", "test pass for pmgen") { }
void help() YS_OVERRIDE
@ -355,12 +236,6 @@ struct TestPmgenPass : public Pass {
if (pattern == "xilinx_srl.variable")
return GENERATE_PATTERN(xilinx_srl_pm, variable);
if (pattern == "peepopt-muldiv")
return GENERATE_PATTERN(peepopt_pm, muldiv);
if (pattern == "peepopt-shiftmul")
return GENERATE_PATTERN(peepopt_pm, shiftmul);
log_cmd_error("Unknown pattern: %s\n", pattern.c_str());
}

646
passes/pmgen/xilinx_dsp.cc Normal file
View File

@ -0,0 +1,646 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
* 2019 Eddie Hung <eddie@fpgeh.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "kernel/yosys.h"
#include "kernel/sigtools.h"
#include <deque>
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
#include "passes/pmgen/xilinx_dsp_pm.h"
#include "passes/pmgen/xilinx_dsp_CREG_pm.h"
#include "passes/pmgen/xilinx_dsp_cascade_pm.h"
static Cell* addDsp(Module *module) {
Cell *cell = module->addCell(NEW_ID, ID(DSP48E1));
cell->setParam(ID(ACASCREG), 0);
cell->setParam(ID(ADREG), 0);
cell->setParam(ID(A_INPUT), Const("DIRECT"));
cell->setParam(ID(ALUMODEREG), 0);
cell->setParam(ID(AREG), 0);
cell->setParam(ID(BCASCREG), 0);
cell->setParam(ID(B_INPUT), Const("DIRECT"));
cell->setParam(ID(BREG), 0);
cell->setParam(ID(CARRYINREG), 0);
cell->setParam(ID(CARRYINSELREG), 0);
cell->setParam(ID(CREG), 0);
cell->setParam(ID(DREG), 0);
cell->setParam(ID(INMODEREG), 0);
cell->setParam(ID(MREG), 0);
cell->setParam(ID(OPMODEREG), 0);
cell->setParam(ID(PREG), 0);
cell->setParam(ID(USE_MULT), Const("NONE"));
cell->setParam(ID(USE_SIMD), Const("ONE48"));
cell->setParam(ID(USE_DPORT), Const("FALSE"));
cell->setPort(ID(D), Const(0, 25));
cell->setPort(ID(INMODE), Const(0, 5));
cell->setPort(ID(ALUMODE), Const(0, 4));
cell->setPort(ID(OPMODE), Const(0, 7));
cell->setPort(ID(CARRYINSEL), Const(0, 3));
cell->setPort(ID(ACIN), Const(0, 30));
cell->setPort(ID(BCIN), Const(0, 18));
cell->setPort(ID(PCIN), Const(0, 48));
cell->setPort(ID(CARRYIN), Const(0, 1));
return cell;
}
void xilinx_simd_pack(Module *module, const std::vector<Cell*> &selected_cells)
{
std::deque<Cell*> simd12_add, simd12_sub;
std::deque<Cell*> simd24_add, simd24_sub;
for (auto cell : selected_cells) {
if (!cell->type.in(ID($add), ID($sub)))
continue;
SigSpec Y = cell->getPort(ID(Y));
if (!Y.is_chunk())
continue;
if (!Y.as_chunk().wire->get_strpool_attribute(ID(use_dsp)).count("simd"))
continue;
if (GetSize(Y) > 25)
continue;
SigSpec A = cell->getPort(ID(A));
SigSpec B = cell->getPort(ID(B));
if (GetSize(Y) <= 13) {
if (GetSize(A) > 12)
continue;
if (GetSize(B) > 12)
continue;
if (cell->type == ID($add))
simd12_add.push_back(cell);
else if (cell->type == ID($sub))
simd12_sub.push_back(cell);
}
else if (GetSize(Y) <= 25) {
if (GetSize(A) > 24)
continue;
if (GetSize(B) > 24)
continue;
if (cell->type == ID($add))
simd24_add.push_back(cell);
else if (cell->type == ID($sub))
simd24_sub.push_back(cell);
}
else
log_abort();
}
auto f12 = [module](SigSpec &AB, SigSpec &C, SigSpec &P, SigSpec &CARRYOUT, Cell *lane) {
SigSpec A = lane->getPort(ID(A));
SigSpec B = lane->getPort(ID(B));
SigSpec Y = lane->getPort(ID(Y));
A.extend_u0(12, lane->getParam(ID(A_SIGNED)).as_bool());
B.extend_u0(12, lane->getParam(ID(B_SIGNED)).as_bool());
AB.append(A);
C.append(B);
if (GetSize(Y) < 13)
Y.append(module->addWire(NEW_ID, 13-GetSize(Y)));
else
log_assert(GetSize(Y) == 13);
P.append(Y.extract(0, 12));
CARRYOUT.append(Y[12]);
};
auto g12 = [&f12,module](std::deque<Cell*> &simd12) {
while (simd12.size() > 1) {
SigSpec AB, C, P, CARRYOUT;
Cell *lane1 = simd12.front();
simd12.pop_front();
Cell *lane2 = simd12.front();
simd12.pop_front();
Cell *lane3 = nullptr;
Cell *lane4 = nullptr;
if (!simd12.empty()) {
lane3 = simd12.front();
simd12.pop_front();
if (!simd12.empty()) {
lane4 = simd12.front();
simd12.pop_front();
}
}
log("Analysing %s.%s for Xilinx DSP SIMD12 packing.\n", log_id(module), log_id(lane1));
Cell *cell = addDsp(module);
cell->setParam(ID(USE_SIMD), Const("FOUR12"));
// X = A:B
// Y = 0
// Z = C
cell->setPort(ID(OPMODE), Const::from_string("0110011"));
log_assert(lane1);
log_assert(lane2);
f12(AB, C, P, CARRYOUT, lane1);
f12(AB, C, P, CARRYOUT, lane2);
if (lane3) {
f12(AB, C, P, CARRYOUT, lane3);
if (lane4)
f12(AB, C, P, CARRYOUT, lane4);
else {
AB.append(Const(0, 12));
C.append(Const(0, 12));
P.append(module->addWire(NEW_ID, 12));
CARRYOUT.append(module->addWire(NEW_ID, 1));
}
}
else {
AB.append(Const(0, 24));
C.append(Const(0, 24));
P.append(module->addWire(NEW_ID, 24));
CARRYOUT.append(module->addWire(NEW_ID, 2));
}
log_assert(GetSize(AB) == 48);
log_assert(GetSize(C) == 48);
log_assert(GetSize(P) == 48);
log_assert(GetSize(CARRYOUT) == 4);
cell->setPort(ID(A), AB.extract(18, 30));
cell->setPort(ID(B), AB.extract(0, 18));
cell->setPort(ID(C), C);
cell->setPort(ID(P), P);
cell->setPort(ID(CARRYOUT), CARRYOUT);
if (lane1->type == ID($sub))
cell->setPort(ID(ALUMODE), Const::from_string("0011"));
module->remove(lane1);
module->remove(lane2);
if (lane3) module->remove(lane3);
if (lane4) module->remove(lane4);
module->design->select(module, cell);
}
};
g12(simd12_add);
g12(simd12_sub);
auto f24 = [module](SigSpec &AB, SigSpec &C, SigSpec &P, SigSpec &CARRYOUT, Cell *lane) {
SigSpec A = lane->getPort(ID(A));
SigSpec B = lane->getPort(ID(B));
SigSpec Y = lane->getPort(ID(Y));
A.extend_u0(24, lane->getParam(ID(A_SIGNED)).as_bool());
B.extend_u0(24, lane->getParam(ID(B_SIGNED)).as_bool());
C.append(A);
AB.append(B);
if (GetSize(Y) < 25)
Y.append(module->addWire(NEW_ID, 25-GetSize(Y)));
else
log_assert(GetSize(Y) == 25);
P.append(Y.extract(0, 24));
CARRYOUT.append(module->addWire(NEW_ID)); // TWO24 uses every other bit
CARRYOUT.append(Y[24]);
};
auto g24 = [&f24,module](std::deque<Cell*> &simd24) {
while (simd24.size() > 1) {
SigSpec AB;
SigSpec C;
SigSpec P;
SigSpec CARRYOUT;
Cell *lane1 = simd24.front();
simd24.pop_front();
Cell *lane2 = simd24.front();
simd24.pop_front();
log("Analysing %s.%s for Xilinx DSP SIMD24 packing.\n", log_id(module), log_id(lane1));
Cell *cell = addDsp(module);
cell->setParam(ID(USE_SIMD), Const("TWO24"));
// X = A:B
// Y = 0
// Z = C
cell->setPort(ID(OPMODE), Const::from_string("0110011"));
log_assert(lane1);
log_assert(lane2);
f24(AB, C, P, CARRYOUT, lane1);
f24(AB, C, P, CARRYOUT, lane2);
log_assert(GetSize(AB) == 48);
log_assert(GetSize(C) == 48);
log_assert(GetSize(P) == 48);
log_assert(GetSize(CARRYOUT) == 4);
cell->setPort(ID(A), AB.extract(18, 30));
cell->setPort(ID(B), AB.extract(0, 18));
cell->setPort(ID(C), C);
cell->setPort(ID(P), P);
cell->setPort(ID(CARRYOUT), CARRYOUT);
if (lane1->type == ID($sub))
cell->setPort(ID(ALUMODE), Const::from_string("0011"));
module->remove(lane1);
module->remove(lane2);
module->design->select(module, cell);
}
};
g24(simd24_add);
g24(simd24_sub);
}
void xilinx_dsp_pack(xilinx_dsp_pm &pm)
{
auto &st = pm.st_xilinx_dsp_pack;
log("Analysing %s.%s for Xilinx DSP packing.\n", log_id(pm.module), log_id(st.dsp));
log_debug("preAdd: %s\n", log_id(st.preAdd, "--"));
log_debug("ffAD: %s %s %s\n", log_id(st.ffAD, "--"), log_id(st.ffADcemux, "--"), log_id(st.ffADrstmux, "--"));
log_debug("ffA2: %s %s %s\n", log_id(st.ffA2, "--"), log_id(st.ffA2cemux, "--"), log_id(st.ffA2rstmux, "--"));
log_debug("ffA1: %s %s %s\n", log_id(st.ffA1, "--"), log_id(st.ffA1cemux, "--"), log_id(st.ffA1rstmux, "--"));
log_debug("ffB2: %s %s %s\n", log_id(st.ffB2, "--"), log_id(st.ffB2cemux, "--"), log_id(st.ffB2rstmux, "--"));
log_debug("ffB1: %s %s %s\n", log_id(st.ffB1, "--"), log_id(st.ffB1cemux, "--"), log_id(st.ffB1rstmux, "--"));
log_debug("ffD: %s %s %s\n", log_id(st.ffD, "--"), log_id(st.ffDcemux, "--"), log_id(st.ffDrstmux, "--"));
log_debug("dsp: %s\n", log_id(st.dsp, "--"));
log_debug("ffM: %s %s %s\n", log_id(st.ffM, "--"), log_id(st.ffMcemux, "--"), log_id(st.ffMrstmux, "--"));
log_debug("postAdd: %s\n", log_id(st.postAdd, "--"));
log_debug("postAddMux: %s\n", log_id(st.postAddMux, "--"));
log_debug("ffP: %s %s %s\n", log_id(st.ffP, "--"), log_id(st.ffPcemux, "--"), log_id(st.ffPrstmux, "--"));
log_debug("overflow: %s\n", log_id(st.overflow, "--"));
Cell *cell = st.dsp;
if (st.preAdd) {
log(" preadder %s (%s)\n", log_id(st.preAdd), log_id(st.preAdd->type));
bool A_SIGNED = st.preAdd->getParam(ID(A_SIGNED)).as_bool();
bool D_SIGNED = st.preAdd->getParam(ID(B_SIGNED)).as_bool();
if (st.sigA == st.preAdd->getPort(ID(B)))
std::swap(A_SIGNED, D_SIGNED);
st.sigA.extend_u0(30, A_SIGNED);
st.sigD.extend_u0(25, D_SIGNED);
cell->setPort(ID(A), st.sigA);
cell->setPort(ID(D), st.sigD);
cell->setPort(ID(INMODE), Const::from_string("00100"));
if (st.ffAD) {
if (st.ffADcemux) {
SigSpec S = st.ffADcemux->getPort(ID(S));
cell->setPort(ID(CEAD), st.ffADcepol ? S : pm.module->Not(NEW_ID, S));
}
else
cell->setPort(ID(CEAD), State::S1);
cell->setParam(ID(ADREG), 1);
}
cell->setParam(ID(USE_DPORT), Const("TRUE"));
pm.autoremove(st.preAdd);
}
if (st.postAdd) {
log(" postadder %s (%s)\n", log_id(st.postAdd), log_id(st.postAdd->type));
SigSpec &opmode = cell->connections_.at(ID(OPMODE));
if (st.postAddMux) {
log_assert(st.ffP);
opmode[4] = st.postAddMux->getPort(ID(S));
pm.autoremove(st.postAddMux);
}
else if (st.ffP && st.sigC == st.sigP)
opmode[4] = State::S0;
else
opmode[4] = State::S1;
opmode[6] = State::S0;
opmode[5] = State::S1;
if (opmode[4] != State::S0) {
if (st.postAddMuxAB == ID(A))
st.sigC.extend_u0(48, st.postAdd->getParam(ID(B_SIGNED)).as_bool());
else
st.sigC.extend_u0(48, st.postAdd->getParam(ID(A_SIGNED)).as_bool());
cell->setPort(ID(C), st.sigC);
}
pm.autoremove(st.postAdd);
}
if (st.overflow) {
log(" overflow %s (%s)\n", log_id(st.overflow), log_id(st.overflow->type));
cell->setParam(ID(USE_PATTERN_DETECT), Const("PATDET"));
cell->setParam(ID(SEL_PATTERN), Const("PATTERN"));
cell->setParam(ID(SEL_MASK), Const("MASK"));
if (st.overflow->type == ID($ge)) {
Const B = st.overflow->getPort(ID(B)).as_const();
log_assert(std::count(B.bits.begin(), B.bits.end(), State::S1) == 1);
// Since B is an exact power of 2, subtract 1
// by inverting all bits up until hitting
// that one hi bit
for (auto &b : B.bits)
if (b == State::S0) b = State::S1;
else if (b == State::S1) {
b = State::S0;
break;
}
B.extu(48);
cell->setParam(ID(MASK), B);
cell->setParam(ID(PATTERN), Const(0, 48));
cell->setPort(ID(OVERFLOW), st.overflow->getPort(ID(Y)));
}
else log_abort();
pm.autoremove(st.overflow);
}
if (st.clock != SigBit())
{
cell->setPort(ID(CLK), st.clock);
auto f = [&pm,cell](SigSpec &A, Cell* ff, Cell* cemux, bool cepol, IdString ceport, Cell* rstmux, bool rstpol, IdString rstport) {
SigSpec D = ff->getPort(ID(D));
SigSpec Q = pm.sigmap(ff->getPort(ID(Q)));
if (!A.empty())
A.replace(Q, D);
if (rstmux) {
SigSpec Y = rstmux->getPort(ID(Y));
SigSpec AB = rstmux->getPort(rstpol ? ID(A) : ID(B));
if (!A.empty())
A.replace(Y, AB);
if (rstport != IdString()) {
SigSpec S = rstmux->getPort(ID(S));
cell->setPort(rstport, rstpol ? S : pm.module->Not(NEW_ID, S));
}
}
else if (rstport != IdString())
cell->setPort(rstport, State::S0);
if (cemux) {
SigSpec Y = cemux->getPort(ID(Y));
SigSpec BA = cemux->getPort(cepol ? ID(B) : ID(A));
SigSpec S = cemux->getPort(ID(S));
if (!A.empty())
A.replace(Y, BA);
cell->setPort(ceport, cepol ? S : pm.module->Not(NEW_ID, S));
}
else
cell->setPort(ceport, State::S1);
for (auto c : Q.chunks()) {
auto it = c.wire->attributes.find(ID(init));
if (it == c.wire->attributes.end())
continue;
for (int i = c.offset; i < c.offset+c.width; i++) {
log_assert(it->second[i] == State::S0 || it->second[i] == State::Sx);
it->second[i] = State::Sx;
}
}
};
if (st.ffA2) {
SigSpec A = cell->getPort(ID(A));
f(A, st.ffA2, st.ffA2cemux, st.ffA2cepol, ID(CEA2), st.ffA2rstmux, st.ffArstpol, ID(RSTA));
if (st.ffA1) {
f(A, st.ffA1, st.ffA1cemux, st.ffA1cepol, ID(CEA1), st.ffA1rstmux, st.ffArstpol, IdString());
cell->setParam(ID(AREG), 2);
cell->setParam(ID(ACASCREG), 2);
}
else {
cell->setParam(ID(AREG), 1);
cell->setParam(ID(ACASCREG), 1);
}
pm.add_siguser(A, cell);
cell->setPort(ID(A), A);
}
if (st.ffB2) {
SigSpec B = cell->getPort(ID(B));
f(B, st.ffB2, st.ffB2cemux, st.ffB2cepol, ID(CEB2), st.ffB2rstmux, st.ffBrstpol, ID(RSTB));
if (st.ffB1) {
f(B, st.ffB1, st.ffB1cemux, st.ffB1cepol, ID(CEB1), st.ffB1rstmux, st.ffBrstpol, IdString());
cell->setParam(ID(BREG), 2);
cell->setParam(ID(BCASCREG), 2);
}
else {
cell->setParam(ID(BREG), 1);
cell->setParam(ID(BCASCREG), 1);
}
pm.add_siguser(B, cell);
cell->setPort(ID(B), B);
}
if (st.ffD) {
SigSpec D = cell->getPort(ID(D));
f(D, st.ffD, st.ffDcemux, st.ffDcepol, ID(CED), st.ffDrstmux, st.ffDrstpol, ID(RSTD));
pm.add_siguser(D, cell);
cell->setPort(ID(D), D);
cell->setParam(ID(DREG), 1);
}
if (st.ffM) {
SigSpec M; // unused
f(M, st.ffM, st.ffMcemux, st.ffMcepol, ID(CEM), st.ffMrstmux, st.ffMrstpol, ID(RSTM));
st.ffM->connections_.at(ID(Q)).replace(st.sigM, pm.module->addWire(NEW_ID, GetSize(st.sigM)));
cell->setParam(ID(MREG), State::S1);
}
if (st.ffP) {
SigSpec P; // unused
f(P, st.ffP, st.ffPcemux, st.ffPcepol, ID(CEP), st.ffPrstmux, st.ffPrstpol, ID(RSTP));
st.ffP->connections_.at(ID(Q)).replace(st.sigP, pm.module->addWire(NEW_ID, GetSize(st.sigP)));
cell->setParam(ID(PREG), State::S1);
}
log(" clock: %s (%s)", log_signal(st.clock), "posedge");
if (st.ffA2) {
log(" ffA2:%s", log_id(st.ffA2));
if (st.ffA1)
log(" ffA1:%s", log_id(st.ffA1));
}
if (st.ffAD)
log(" ffAD:%s", log_id(st.ffAD));
if (st.ffB2) {
log(" ffB2:%s", log_id(st.ffB2));
if (st.ffB1)
log(" ffB1:%s", log_id(st.ffB1));
}
if (st.ffD)
log(" ffD:%s", log_id(st.ffD));
if (st.ffM)
log(" ffM:%s", log_id(st.ffM));
if (st.ffP)
log(" ffP:%s", log_id(st.ffP));
}
log("\n");
SigSpec P = st.sigP;
if (GetSize(P) < 48)
P.append(pm.module->addWire(NEW_ID, 48-GetSize(P)));
cell->setPort(ID(P), P);
pm.blacklist(cell);
}
void xilinx_dsp_packC(xilinx_dsp_CREG_pm &pm)
{
auto &st = pm.st_xilinx_dsp_packC;
log_debug("Analysing %s.%s for Xilinx DSP packing (CREG).\n", log_id(pm.module), log_id(st.dsp));
log_debug("ffC: %s %s %s\n", log_id(st.ffC, "--"), log_id(st.ffCcemux, "--"), log_id(st.ffCrstmux, "--"));
Cell *cell = st.dsp;
if (st.clock != SigBit())
{
cell->setPort(ID(CLK), st.clock);
auto f = [&pm,cell](SigSpec &A, Cell* ff, Cell* cemux, bool cepol, IdString ceport, Cell* rstmux, bool rstpol, IdString rstport) {
SigSpec D = ff->getPort(ID(D));
SigSpec Q = pm.sigmap(ff->getPort(ID(Q)));
if (!A.empty())
A.replace(Q, D);
if (rstmux) {
SigSpec Y = rstmux->getPort(ID(Y));
SigSpec AB = rstmux->getPort(rstpol ? ID(A) : ID(B));
if (!A.empty())
A.replace(Y, AB);
if (rstport != IdString()) {
SigSpec S = rstmux->getPort(ID(S));
cell->setPort(rstport, rstpol ? S : pm.module->Not(NEW_ID, S));
}
}
else if (rstport != IdString())
cell->setPort(rstport, State::S0);
if (cemux) {
SigSpec Y = cemux->getPort(ID(Y));
SigSpec BA = cemux->getPort(cepol ? ID(B) : ID(A));
SigSpec S = cemux->getPort(ID(S));
if (!A.empty())
A.replace(Y, BA);
cell->setPort(ceport, cepol ? S : pm.module->Not(NEW_ID, S));
}
else
cell->setPort(ceport, State::S1);
for (auto c : Q.chunks()) {
auto it = c.wire->attributes.find(ID(init));
if (it == c.wire->attributes.end())
continue;
for (int i = c.offset; i < c.offset+c.width; i++) {
log_assert(it->second[i] == State::S0 || it->second[i] == State::Sx);
it->second[i] = State::Sx;
}
}
};
if (st.ffC) {
SigSpec C = cell->getPort(ID(C));
f(C, st.ffC, st.ffCcemux, st.ffCcepol, ID(CEC), st.ffCrstmux, st.ffCrstpol, ID(RSTC));
pm.add_siguser(C, cell);
cell->setPort(ID(C), C);
cell->setParam(ID(CREG), 1);
}
log(" clock: %s (%s)", log_signal(st.clock), "posedge");
if (st.ffC)
log(" ffC:%s", log_id(st.ffC));
log("\n");
}
pm.blacklist(cell);
}
struct XilinxDspPass : public Pass {
XilinxDspPass() : Pass("xilinx_dsp", "Xilinx: pack resources into DSPs") { }
void help() YS_OVERRIDE
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" xilinx_dsp [options] [selection]\n");
log("\n");
log("Pack input registers (A2, A1, B2, B1, C, D, AD; with optional enable/reset),\n");
log("pipeline registers (M; with optional enable/reset), output registers (P; with\n");
log("optional enable/reset), pre-adder and/or post-adder into Xilinx DSP resources.\n");
log("\n");
log("Multiply-accumulate operations using the post-adder with feedback on the 'C'\n");
log("input will be folded into the DSP. In this scenario only, the 'C' input can be\n");
log("used to override the current accumulation result with a new value, which will\n");
log("be added to the multiplier result to form the next accumulation result.\n");
log("\n");
log("Use of the dedicated 'PCOUT' -> 'PCIN' cascade path is detected for 'P' -> 'C'\n");
log("connections (optionally, where 'P' is right-shifted by 17-bits and used as an\n");
log("input to the post-adder -- a pattern common for summing partial products to\n");
log("implement wide multipliers). Limited support also exists for similar cascading\n");
log("for A and B using '[AB]COUT' -> '[AB]CIN'. Currently, cascade chains are limited\n");
log("to a maximum length of 20 cells, corresponding to the smallest Xilinx 7 Series\n");
log("device.\n");
log("\n");
log("\n");
log("Experimental feature: addition/subtractions less than 12 or 24 bits with the\n");
log("'(* use_dsp=\"simd\" *)' attribute attached to the output wire or attached to\n");
log("the add/subtract operator will cause those operations to be implemented using\n");
log("the 'SIMD' feature of DSPs.\n");
log("\n");
log("Experimental feature: the presence of a `$ge' cell attached to the registered\n");
log("P output implementing the operation \"(P >= <power-of-2>)\" will be transformed\n");
log("into using the DSP48E1's pattern detector feature for overflow detection.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
{
log_header(design, "Executing XILINX_DSP pass (pack resources into DSPs).\n");
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
{
// if (args[argidx] == "-singleton") {
// singleton_mode = true;
// continue;
// }
break;
}
extra_args(args, argidx, design);
for (auto module : design->selected_modules()) {
// Experimental feature: pack $add/$sub cells with
// (* use_dsp48="simd" *) into DSP48E1's using its
// SIMD feature
xilinx_simd_pack(module, module->selected_cells());
// Match for all features ([ABDMP][12]?REG, pre-adder,
// post-adder, pattern detector, etc.) except for CREG
{
xilinx_dsp_pm pm(module, module->selected_cells());
pm.run_xilinx_dsp_pack(xilinx_dsp_pack);
}
// Separating out CREG packing is necessary since there
// is no guarantee that the cell ordering corresponds
// to the "expected" case (i.e. the order in which
// they appear in the source) thus the possiblity
// existed that a register got packed as a CREG into a
// downstream DSP that should have otherwise been a
// PREG of an upstream DSP that had not been visited
// yet
{
xilinx_dsp_CREG_pm pm(module, module->selected_cells());
pm.run_xilinx_dsp_packC(xilinx_dsp_packC);
}
// Lastly, identify and utilise PCOUT -> PCIN,
// ACOUT -> ACIN, and BCOUT-> BCIN dedicated cascade
// chains
{
xilinx_dsp_cascade_pm pm(module, module->selected_cells());
pm.run_xilinx_dsp_cascade();
}
}
}
} XilinxDspPass;
PRIVATE_NAMESPACE_END

727
passes/pmgen/xilinx_dsp.pmg Normal file
View File

@ -0,0 +1,727 @@
// This file describes the main pattern matcher setup (of three total) that
// forms the `xilinx_dsp` pass described in xilinx_dsp.cc
// At a high level, it works as follows:
// ( 1) Starting from a DSP48E1 cell
// ( 2) Match the driver of the 'A' input to a possible $dff cell (ADREG)
// (attached to at most two $mux cells that implement clock-enable or
// reset functionality, using a subpattern discussed below)
// If ADREG matched, treat 'A' input as input of ADREG
// ( 3) Match the driver of the 'A' and 'D' inputs for a possible $add cell
// (pre-adder)
// ( 4) If pre-adder was present, find match 'A' input for A2REG
// If pre-adder was not present, move ADREG to A2REG
// If A2REG, then match 'A' input for A1REG
// ( 5) Match 'B' input for B2REG
// If B2REG, then match 'B' input for B1REG
// ( 6) Match 'D' input for DREG
// ( 7) Match 'P' output that exclusively drives an MREG
// ( 8) Match 'P' output that exclusively drives one of two inputs to an $add
// cell (post-adder).
// The other input to the adder is assumed to come in from the 'C' input
// (note: 'P' -> 'C' connections that exist for accumulators are
// recognised in xilinx_dsp.cc).
// ( 9) Match 'P' output that exclusively drives a PREG
// (10) If post-adder and PREG both present, match for a $mux cell driving
// the 'C' input, where one of the $mux's inputs is the PREG output.
// This indicates an accumulator situation, and one where a $mux exists
// to override the accumulated value:
// +--------------------------------+
// | ____ |
// +--| \ |
// |$mux|-+ |
// 'C' ---|____/ | |
// | /-------\ +----+ |
// +----+ +-| post- |___|PREG|---+ 'P'
// |MREG|------ | adder | +----+
// +----+ \-------/
// (11) If PREG present, match for a greater-than-or-equal $ge cell attached
// to the 'P' output where it is compared to a constant that is a
// power-of-2: e.g. `assign overflow = (PREG >= 2**40);`
// In this scenario, the pattern detector functionality of a DSP48E1 can
// to implement this function
// Notes:
// - The intention of this pattern matcher is for it to be compatible with
// DSP48E1 cells inferred from multiply operations by Yosys, as well as for
// user instantiations that may already contain the cells being packed...
// (though the latter is currently untested)
// - Since the $dff-with-optional-clock-enable-or-reset-mux pattern is used
// for each *REG match, it has been factored out into two subpatterns:
// in_dffe and out_dffe located at the bottom of this file.
// - Matching for pattern detector features is currently incomplete. For
// example, matching for underflow as well as overflow detection is
// possible, as would auto-reset, enabling saturated arithmetic, detecting
// custom patterns, etc.
pattern xilinx_dsp_pack
state <SigBit> clock
state <SigSpec> sigA sigB sigC sigD sigM sigP
state <IdString> postAddAB postAddMuxAB
state <bool> ffA1cepol ffA2cepol ffADcepol ffB1cepol ffB2cepol ffDcepol ffMcepol ffPcepol
state <bool> ffArstpol ffADrstpol ffBrstpol ffDrstpol ffMrstpol ffPrstpol
state <Cell*> ffAD ffADcemux ffADrstmux ffA1 ffA1cemux ffA1rstmux ffA2 ffA2cemux ffA2rstmux
state <Cell*> ffB1 ffB1cemux ffB1rstmux ffB2 ffB2cemux ffB2rstmux
state <Cell*> ffD ffDcemux ffDrstmux ffM ffMcemux ffMrstmux ffP ffPcemux ffPrstmux
// Variables used for subpatterns
state <SigSpec> argQ argD
state <bool> ffcepol ffrstpol
state <int> ffoffset
udata <SigSpec> dffD dffQ
udata <SigBit> dffclock
udata <Cell*> dff dffcemux dffrstmux
udata <bool> dffcepol dffrstpol
// (1) Starting from a DSP48E1 cell
match dsp
select dsp->type.in(\DSP48E1)
endmatch
code sigA sigB sigC sigD sigM clock
auto unextend = [](const SigSpec &sig) {
int i;
for (i = GetSize(sig)-1; i > 0; i--)
if (sig[i] != sig[i-1])
break;
// Do not remove non-const sign bit
if (sig[i].wire)
++i;
return sig.extract(0, i);
};
sigA = unextend(port(dsp, \A));
sigB = unextend(port(dsp, \B));
sigC = port(dsp, \C, SigSpec());
sigD = port(dsp, \D, SigSpec());
SigSpec P = port(dsp, \P);
if (param(dsp, \USE_MULT, Const("MULTIPLY")).decode_string() == "MULTIPLY") {
// Only care about those bits that are used
int i;
for (i = GetSize(P)-1; i >= 0; i--)
if (nusers(P[i]) > 1)
break;
i++;
log_assert(nusers(P.extract_end(i)) <= 1);
// This sigM could have no users if downstream sinks (e.g. $add) is
// narrower than $mul result, for example
if (i == 0)
reject;
sigM = P.extract(0, i);
}
else
sigM = P;
clock = port(dsp, \CLK, SigBit());
endcode
// (2) Match the driver of the 'A' input to a possible $dff cell (ADREG)
// (attached to at most two $mux cells that implement clock-enable or
// reset functionality, using a subpattern discussed above)
// If matched, treat 'A' input as input of ADREG
code argQ ffAD ffADcemux ffADrstmux ffADcepol ffADrstpol sigA clock
if (param(dsp, \ADREG).as_int() == 0) {
argQ = sigA;
subpattern(in_dffe);
if (dff) {
ffAD = dff;
clock = dffclock;
if (dffrstmux) {
ffADrstmux = dffrstmux;
ffADrstpol = dffrstpol;
}
if (dffcemux) {
ffADcemux = dffcemux;
ffADcepol = dffcepol;
}
sigA = dffD;
}
}
endcode
// (3) Match the driver of the 'A' and 'D' inputs for a possible $add cell
// (pre-adder)
match preAdd
if sigD.empty() || sigD.is_fully_zero()
// Ensure that preAdder not already used
if param(dsp, \USE_DPORT, Const("FALSE")).decode_string() == "FALSE"
if port(dsp, \INMODE, Const(0, 5)).is_fully_zero()
select preAdd->type.in($add)
// Output has to be 25 bits or less
select GetSize(port(preAdd, \Y)) <= 25
select nusers(port(preAdd, \Y)) == 2
choice <IdString> AB {\A, \B}
// A port has to be 30 bits or less
select GetSize(port(preAdd, AB)) <= 30
define <IdString> BA (AB == \A ? \B : \A)
// D port has to be 25 bits or less
select GetSize(port(preAdd, BA)) <= 25
index <SigSpec> port(preAdd, \Y) === sigA
optional
endmatch
code sigA sigD
if (preAdd) {
sigA = port(preAdd, \A);
sigD = port(preAdd, \B);
}
endcode
// (4) If pre-adder was present, find match 'A' input for A2REG
// If pre-adder was not present, move ADREG to A2REG
// Then match 'A' input for A1REG
code argQ ffAD ffADcemux ffADrstmux ffADcepol ffADrstpol sigA clock ffA2 ffA2cemux ffA2rstmux ffA2cepol ffArstpol ffA1 ffA1cemux ffA1rstmux ffA1cepol
// Only search for ffA2 if there was a pre-adder
// (otherwise ffA2 would have been matched as ffAD)
if (preAdd) {
if (param(dsp, \AREG).as_int() == 0) {
argQ = sigA;
subpattern(in_dffe);
if (dff) {
ffA2 = dff;
clock = dffclock;
if (dffrstmux) {
ffA2rstmux = dffrstmux;
ffArstpol = dffrstpol;
}
if (dffcemux) {
ffA2cepol = dffcepol;
ffA2cemux = dffcemux;
}
sigA = dffD;
}
}
}
// And if there wasn't a pre-adder,
// move AD register to A
else if (ffAD) {
log_assert(!ffA2 && !ffA2cemux && !ffA2rstmux);
std::swap(ffA2, ffAD);
std::swap(ffA2cemux, ffADcemux);
std::swap(ffA2rstmux, ffADrstmux);
ffA2cepol = ffADcepol;
ffArstpol = ffADrstpol;
}
// Now attempt to match A1
if (ffA2) {
argQ = sigA;
subpattern(in_dffe);
if (dff) {
if ((ffA2rstmux != nullptr) ^ (dffrstmux != nullptr))
goto ffA1_end;
if (dffrstmux) {
if (ffArstpol != dffrstpol)
goto ffA1_end;
if (port(ffA2rstmux, \S) != port(dffrstmux, \S))
goto ffA1_end;
ffA1rstmux = dffrstmux;
}
ffA1 = dff;
clock = dffclock;
if (dffcemux) {
ffA1cemux = dffcemux;
ffA1cepol = dffcepol;
}
sigA = dffD;
ffA1_end: ;
}
}
endcode
// (5) Match 'B' input for B2REG
// If B2REG, then match 'B' input for B1REG
code argQ ffB2 ffB2cemux ffB2rstmux ffB2cepol ffBrstpol sigB clock ffB1 ffB1cemux ffB1rstmux ffB1cepol
if (param(dsp, \BREG).as_int() == 0) {
argQ = sigB;
subpattern(in_dffe);
if (dff) {
ffB2 = dff;
clock = dffclock;
if (dffrstmux) {
ffB2rstmux = dffrstmux;
ffBrstpol = dffrstpol;
}
if (dffcemux) {
ffB2cemux = dffcemux;
ffB2cepol = dffcepol;
}
sigB = dffD;
// Now attempt to match B1
if (ffB2) {
argQ = sigB;
subpattern(in_dffe);
if (dff) {
if ((ffB2rstmux != nullptr) ^ (dffrstmux != nullptr))
goto ffB1_end;
if (dffrstmux) {
if (ffBrstpol != dffrstpol)
goto ffB1_end;
if (port(ffB2rstmux, \S) != port(dffrstmux, \S))
goto ffB1_end;
ffB1rstmux = dffrstmux;
}
ffB1 = dff;
clock = dffclock;
if (dffcemux) {
ffB1cemux = dffcemux;
ffB1cepol = dffcepol;
}
sigB = dffD;
ffB1_end: ;
}
}
}
}
endcode
// (6) Match 'D' input for DREG
code argQ ffD ffDcemux ffDrstmux ffDcepol ffDrstpol sigD clock
if (param(dsp, \DREG).as_int() == 0) {
argQ = sigD;
subpattern(in_dffe);
if (dff) {
ffD = dff;
clock = dffclock;
if (dffrstmux) {
ffDrstmux = dffrstmux;
ffDrstpol = dffrstpol;
}
if (dffcemux) {
ffDcemux = dffcemux;
ffDcepol = dffcepol;
}
sigD = dffD;
}
}
endcode
// (7) Match 'P' output that exclusively drives an MREG
code argD ffM ffMcemux ffMrstmux ffMcepol ffMrstpol sigM sigP clock
if (param(dsp, \MREG).as_int() == 0 && nusers(sigM) == 2) {
argD = sigM;
subpattern(out_dffe);
if (dff) {
ffM = dff;
clock = dffclock;
if (dffrstmux) {
ffMrstmux = dffrstmux;
ffMrstpol = dffrstpol;
}
if (dffcemux) {
ffMcemux = dffcemux;
ffMcepol = dffcepol;
}
sigM = dffQ;
}
}
sigP = sigM;
endcode
// (8) Match 'P' output that exclusively drives one of two inputs to an $add
// cell (post-adder).
// The other input to the adder is assumed to come in from the 'C' input
// (note: 'P' -> 'C' connections that exist for accumulators are
// recognised in xilinx_dsp.cc).
match postAdd
// Ensure that Z mux is not already used
if port(dsp, \OPMODE, SigSpec()).extract(4,3).is_fully_zero()
select postAdd->type.in($add)
select GetSize(port(postAdd, \Y)) <= 48
choice <IdString> AB {\A, \B}
select nusers(port(postAdd, AB)) <= 3
filter ffMcemux || nusers(port(postAdd, AB)) == 2
filter !ffMcemux || nusers(port(postAdd, AB)) == 3
index <SigBit> port(postAdd, AB)[0] === sigP[0]
filter GetSize(port(postAdd, AB)) >= GetSize(sigP)
filter port(postAdd, AB).extract(0, GetSize(sigP)) == sigP
// Check that remainder of AB is a sign-extension
define <bool> AB_SIGNED (param(postAdd, AB == \A ? \A_SIGNED : \B_SIGNED).as_bool())
filter port(postAdd, AB).extract_end(GetSize(sigP)) == SigSpec(AB_SIGNED ? sigP[GetSize(sigP)-1] : State::S0, GetSize(port(postAdd, AB))-GetSize(sigP))
set postAddAB AB
optional
endmatch
code sigC sigP
if (postAdd) {
sigC = port(postAdd, postAddAB == \A ? \B : \A);
sigP = port(postAdd, \Y);
}
endcode
// (9) Match 'P' output that exclusively drives a PREG
code argD ffP ffPcemux ffPrstmux ffPcepol ffPrstpol sigP clock
if (param(dsp, \PREG).as_int() == 0) {
int users = 2;
// If ffMcemux and no postAdd new-value net must have three users: ffMcemux, ffM and ffPcemux
if (ffMcemux && !postAdd) users++;
if (nusers(sigP) == users) {
argD = sigP;
subpattern(out_dffe);
if (dff) {
ffP = dff;
clock = dffclock;
if (dffrstmux) {
ffPrstmux = dffrstmux;
ffPrstpol = dffrstpol;
}
if (dffcemux) {
ffPcemux = dffcemux;
ffPcepol = dffcepol;
}
sigP = dffQ;
}
}
}
endcode
// (10) If post-adder and PREG both present, match for a $mux cell driving
// the 'C' input, where one of the $mux's inputs is the PREG output.
// This indicates an accumulator situation, and one where a $mux exists
// to override the accumulated value:
// +--------------------------------+
// | ____ |
// +--| \ |
// |$mux|-+ |
// 'C' ---|____/ | |
// | /-------\ +----+ |
// +----+ +-| post- |___|PREG|---+ 'P'
// |MREG|------ | adder | +----+
// +----+ \-------/
match postAddMux
if postAdd
if ffP
select postAddMux->type.in($mux)
select nusers(port(postAddMux, \Y)) == 2
choice <IdString> AB {\A, \B}
index <SigSpec> port(postAddMux, AB) === sigP
index <SigSpec> port(postAddMux, \Y) === sigC
set postAddMuxAB AB
optional
endmatch
code sigC
if (postAddMux)
sigC = port(postAddMux, postAddMuxAB == \A ? \B : \A);
endcode
// (11) If PREG present, match for a greater-than-or-equal $ge cell attached to
// the 'P' output where it is compared to a constant that is a power-of-2:
// e.g. `assign overflow = (PREG >= 2**40);`
// In this scenario, the pattern detector functionality of a DSP48E1 can
// to implement this function
match overflow
if ffP
if param(dsp, \USE_PATTERN_DETECT, Const("NO_PATDET")).decode_string() == "NO_PATDET"
select overflow->type.in($ge)
select GetSize(port(overflow, \Y)) <= 48
select port(overflow, \B).is_fully_const()
define <Const> B port(overflow, \B).as_const()
select std::count(B.bits.begin(), B.bits.end(), State::S1) == 1
index <SigSpec> port(overflow, \A) === sigP
optional
endmatch
code
accept;
endcode
// #######################
// Subpattern for matching against input registers, based on knowledge of the
// 'Q' input. Typically, identifying registers with clock-enable and reset
// capability would be a task would be handled by other Yosys passes such as
// dff2dffe, but since DSP inference happens much before this, these patterns
// have to be manually identified.
// At a high level:
// (1) Starting from a $dff cell that (partially or fully) drives the given
// 'Q' argument
// (2) Match for a $mux cell implementing synchronous reset semantics ---
// one that exclusively drives the 'D' input of the $dff, with one of its
// $mux inputs being fully zero
// (3) Match for a $mux cell implement clock enable semantics --- one that
// exclusively drives the 'D' input of the $dff (or the other input of
// the reset $mux) and where one of this $mux's inputs is connected to
// the 'Q' output of the $dff
subpattern in_dffe
arg argD argQ clock
code
dff = nullptr;
if (GetSize(argQ) == 0)
reject;
for (const auto &c : argQ.chunks()) {
// Abandon matches when 'Q' is a constant
if (!c.wire)
reject;
// Abandon matches when 'Q' has the keep attribute set
if (c.wire->get_bool_attribute(\keep))
reject;
// Abandon matches when 'Q' has a non-zero init attribute set
// (not supported by DSP48E1)
Const init = c.wire->attributes.at(\init, Const());
if (!init.empty())
for (auto b : init.extract(c.offset, c.width))
if (b != State::Sx && b != State::S0)
reject;
}
endcode
// (1) Starting from a $dff cell that (partially or fully) drives the given
// 'Q' argument
match ff
select ff->type.in($dff)
// DSP48E1 does not support clock inversion
select param(ff, \CLK_POLARITY).as_bool()
slice offset GetSize(port(ff, \D))
index <SigBit> port(ff, \Q)[offset] === argQ[0]
// Check that the rest of argQ is present
filter GetSize(port(ff, \Q)) >= offset + GetSize(argQ)
filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
filter clock == SigBit() || port(ff, \CLK) == clock
set ffoffset offset
endmatch
code argQ argD
SigSpec Q = port(ff, \Q);
dff = ff;
dffclock = port(ff, \CLK);
dffD = argQ;
argD = port(ff, \D);
argQ = Q;
dffD.replace(argQ, argD);
// Only search for ffrstmux if dffD only
// has two (ff, ffrstmux) users
if (nusers(dffD) > 2)
argD = SigSpec();
endcode
// (2) Match for a $mux cell implementing synchronous reset semantics ---
// exclusively drives the 'D' input of the $dff, with one of the $mux
// inputs being fully zero
match ffrstmux
if !argD.empty()
select ffrstmux->type.in($mux)
index <SigSpec> port(ffrstmux, \Y) === argD
choice <IdString> BA {\B, \A}
// DSP48E1 only supports reset to zero
select port(ffrstmux, BA).is_fully_zero()
define <bool> pol (BA == \B)
set ffrstpol pol
semioptional
endmatch
code argD
if (ffrstmux) {
dffrstmux = ffrstmux;
dffrstpol = ffrstpol;
argD = port(ffrstmux, ffrstpol ? \A : \B);
dffD.replace(port(ffrstmux, \Y), argD);
// Only search for ffcemux if argQ has at
// least 3 users (ff, <upstream>, ffrstmux) and
// dffD only has two (ff, ffrstmux)
if (!(nusers(argQ) >= 3 && nusers(dffD) == 2))
argD = SigSpec();
}
else
dffrstmux = nullptr;
endcode
// (3) Match for a $mux cell implement clock enable semantics --- one that
// exclusively drives the 'D' input of the $dff (or the other input of
// the reset $mux) and where one of this $mux's inputs is connected to
// the 'Q' output of the $dff
match ffcemux
if !argD.empty()
select ffcemux->type.in($mux)
index <SigSpec> port(ffcemux, \Y) === argD
choice <IdString> AB {\A, \B}
index <SigSpec> port(ffcemux, AB) === argQ
define <bool> pol (AB == \A)
set ffcepol pol
semioptional
endmatch
code argD
if (ffcemux) {
dffcemux = ffcemux;
dffcepol = ffcepol;
argD = port(ffcemux, ffcepol ? \B : \A);
dffD.replace(port(ffcemux, \Y), argD);
}
else
dffcemux = nullptr;
endcode
// #######################
// Subpattern for matching against output registers, based on knowledge of the
// 'D' input.
// At a high level:
// (1) Starting from an optional $mux cell that implements clock enable
// semantics --- one where the given 'D' argument (partially or fully)
// drives one of its two inputs
// (2) Starting from, or continuing onto, another optional $mux cell that
// implements synchronous reset semantics --- one where the given 'D'
// argument (or the clock enable $mux output) drives one of its two inputs
// and where the other input is fully zero
// (3) Match for a $dff cell (whose 'D' input is the 'D' argument, or the
// output of the previous clock enable or reset $mux cells)
subpattern out_dffe
arg argD argQ clock
code
dff = nullptr;
for (auto c : argD.chunks())
// Abandon matches when 'D' has the keep attribute set
if (c.wire->get_bool_attribute(\keep))
reject;
endcode
// (1) Starting from an optional $mux cell that implements clock enable
// semantics --- one where the given 'D' argument (partially or fully)
// drives one of its two inputs
match ffcemux
select ffcemux->type.in($mux)
// ffcemux output must have two users: ffcemux and ff.D
select nusers(port(ffcemux, \Y)) == 2
choice <IdString> AB {\A, \B}
// keep-last-value net must have at least three users: ffcemux, ff, downstream sink(s)
select nusers(port(ffcemux, AB)) >= 3
slice offset GetSize(port(ffcemux, \Y))
define <IdString> BA (AB == \A ? \B : \A)
index <SigBit> port(ffcemux, BA)[offset] === argD[0]
// Check that the rest of argD is present
filter GetSize(port(ffcemux, BA)) >= offset + GetSize(argD)
filter port(ffcemux, BA).extract(offset, GetSize(argD)) == argD
set ffoffset offset
define <bool> pol (AB == \A)
set ffcepol pol
semioptional
endmatch
code argD argQ
dffcemux = ffcemux;
if (ffcemux) {
SigSpec BA = port(ffcemux, ffcepol ? \B : \A);
SigSpec Y = port(ffcemux, \Y);
argQ = argD;
argD.replace(BA, Y);
argQ.replace(BA, port(ffcemux, ffcepol ? \A : \B));
dffcemux = ffcemux;
dffcepol = ffcepol;
}
endcode
// (2) Starting from, or continuing onto, another optional $mux cell that
// implements synchronous reset semantics --- one where the given 'D'
// argument (or the clock enable $mux output) drives one of its two inputs
// and where the other input is fully zero
match ffrstmux
select ffrstmux->type.in($mux)
// ffrstmux output must have two users: ffrstmux and ff.D
select nusers(port(ffrstmux, \Y)) == 2
choice <IdString> BA {\B, \A}
// DSP48E1 only supports reset to zero
select port(ffrstmux, BA).is_fully_zero()
slice offset GetSize(port(ffrstmux, \Y))
define <IdString> AB (BA == \B ? \A : \B)
index <SigBit> port(ffrstmux, AB)[offset] === argD[0]
// Check that offset is consistent
filter !ffcemux || ffoffset == offset
// Check that the rest of argD is present
filter GetSize(port(ffrstmux, AB)) >= offset + GetSize(argD)
filter port(ffrstmux, AB).extract(offset, GetSize(argD)) == argD
set ffoffset offset
define <bool> pol (AB == \A)
set ffrstpol pol
semioptional
endmatch
code argD argQ
dffrstmux = ffrstmux;
if (ffrstmux) {
SigSpec AB = port(ffrstmux, ffrstpol ? \A : \B);
SigSpec Y = port(ffrstmux, \Y);
argD.replace(AB, Y);
dffrstmux = ffrstmux;
dffrstpol = ffrstpol;
}
endcode
// (3) Match for a $dff cell (whose 'D' input is the 'D' argument, or the
// output of the previous clock enable or reset $mux cells)
match ff
select ff->type.in($dff)
// DSP48E1 does not support clock inversion
select param(ff, \CLK_POLARITY).as_bool()
slice offset GetSize(port(ff, \D))
index <SigBit> port(ff, \D)[offset] === argD[0]
// Check that offset is consistent
filter (!ffcemux && !ffrstmux) || ffoffset == offset
// Check that the rest of argD is present
filter GetSize(port(ff, \D)) >= offset + GetSize(argD)
filter port(ff, \D).extract(offset, GetSize(argD)) == argD
// Check that FF.Q is connected to CE-mux
filter !ffcemux || port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
filter clock == SigBit() || port(ff, \CLK) == clock
set ffoffset offset
endmatch
code argQ
SigSpec D = port(ff, \D);
SigSpec Q = port(ff, \Q);
if (!ffcemux) {
argQ = argD;
argQ.replace(D, Q);
}
// Abandon matches when 'Q' has a non-zero init attribute set
// (not supported by DSP48E1)
for (auto c : argQ.chunks()) {
Const init = c.wire->attributes.at(\init, Const());
if (!init.empty())
for (auto b : init.extract(c.offset, c.width))
if (b != State::Sx && b != State::S0)
reject;
}
dff = ff;
dffQ = argQ;
dffclock = port(ff, \CLK);
endcode

View File

@ -0,0 +1,234 @@
// This file describes the second of three pattern matcher setups that
// forms the `xilinx_dsp` pass described in xilinx_dsp.cc
// At a high level, it works as follows:
// (1) Starting from a DSP48E1 cell that (a) doesn't have a CREG already,
// and (b) uses the 'C' port
// (2) Match the driver of the 'C' input to a possible $dff cell (CREG)
// (attached to at most two $mux cells that implement clock-enable or
// reset functionality, using a subpattern discussed below)
// Notes:
// - Running CREG packing after xilinx_dsp_pack is necessary since there is no
// guarantee that the cell ordering corresponds to the "expected" case (i.e.
// the order in which they appear in the source) thus the possiblity existed
// that a register got packed as a CREG into a downstream DSP that should
// have otherwise been a PREG of an upstream DSP that had not been visited
// yet
// - The reason this is separated out from the xilinx_dsp.pmg file is
// for efficiency --- each *.pmg file creates a class of the same basename,
// which when constructed, creates a custom database tailored to the
// pattern(s) contained within. Since the pattern in this file must be
// executed after the pattern contained in xilinx_dsp.pmg, it is necessary
// to reconstruct this database. Separating the two patterns into
// independent files causes two smaller, more specific, databases.
pattern xilinx_dsp_packC
udata <std::function<SigSpec(const SigSpec&)>> unextend
state <SigBit> clock
state <SigSpec> sigC sigP
state <bool> ffCcepol ffCrstpol
state <Cell*> ffC ffCcemux ffCrstmux
// Variables used for subpatterns
state <SigSpec> argQ argD
state <bool> ffcepol ffrstpol
state <int> ffoffset
udata <SigSpec> dffD dffQ
udata <SigBit> dffclock
udata <Cell*> dff dffcemux dffrstmux
udata <bool> dffcepol dffrstpol
// (1) Starting from a DSP48E1 cell that (a) doesn't have a CREG already,
// and (b) uses the 'C' port
match dsp
select dsp->type.in(\DSP48E1)
select param(dsp, \CREG, 1).as_int() == 0
select nusers(port(dsp, \C, SigSpec())) > 1
endmatch
code sigC sigP clock
unextend = [](const SigSpec &sig) {
int i;
for (i = GetSize(sig)-1; i > 0; i--)
if (sig[i] != sig[i-1])
break;
// Do not remove non-const sign bit
if (sig[i].wire)
++i;
return sig.extract(0, i);
};
sigC = unextend(port(dsp, \C, SigSpec()));
SigSpec P = port(dsp, \P);
if (param(dsp, \USE_MULT, Const("MULTIPLY")).decode_string() == "MULTIPLY") {
// Only care about those bits that are used
int i;
for (i = GetSize(P)-1; i >= 0; i--)
if (nusers(P[i]) > 1)
break;
i++;
log_assert(nusers(P.extract_end(i)) <= 1);
sigP = P.extract(0, i);
}
else
sigP = P;
clock = port(dsp, \CLK, SigBit());
endcode
// (2) Match the driver of the 'C' input to a possible $dff cell (CREG)
// (attached to at most two $mux cells that implement clock-enable or
// reset functionality, using the in_dffe subpattern)
code argQ ffC ffCcemux ffCrstmux ffCcepol ffCrstpol sigC clock
argQ = sigC;
subpattern(in_dffe);
if (dff) {
ffC = dff;
clock = dffclock;
if (dffrstmux) {
ffCrstmux = dffrstmux;
ffCrstpol = dffrstpol;
}
if (dffcemux) {
ffCcemux = dffcemux;
ffCcepol = dffcepol;
}
sigC = dffD;
}
endcode
code
if (ffC)
accept;
endcode
// #######################
// Subpattern for matching against input registers, based on knowledge of the
// 'Q' input. Typically, identifying registers with clock-enable and reset
// capability would be a task would be handled by other Yosys passes such as
// dff2dffe, but since DSP inference happens much before this, these patterns
// have to be manually identified.
// At a high level:
// (1) Starting from a $dff cell that (partially or fully) drives the given
// 'Q' argument
// (2) Match for a $mux cell implementing synchronous reset semantics ---
// one that exclusively drives the 'D' input of the $dff, with one of its
// $mux inputs being fully zero
// (3) Match for a $mux cell implement clock enable semantics --- one that
// exclusively drives the 'D' input of the $dff (or the other input of
// the reset $mux) and where one of this $mux's inputs is connected to
// the 'Q' output of the $dff
subpattern in_dffe
arg argD argQ clock
code
dff = nullptr;
for (const auto &c : argQ.chunks()) {
// Abandon matches when 'Q' is a constant
if (!c.wire)
reject;
// Abandon matches when 'Q' has the keep attribute set
if (c.wire->get_bool_attribute(\keep))
reject;
// Abandon matches when 'Q' has a non-zero init attribute set
// (not supported by DSP48E1)
Const init = c.wire->attributes.at(\init, Const());
for (auto b : init.extract(c.offset, c.width))
if (b != State::Sx && b != State::S0)
reject;
}
endcode
// (1) Starting from a $dff cell that (partially or fully) drives the given
// 'Q' argument
match ff
select ff->type.in($dff)
// DSP48E1 does not support clock inversion
select param(ff, \CLK_POLARITY).as_bool()
slice offset GetSize(port(ff, \D))
index <SigBit> port(ff, \Q)[offset] === argQ[0]
// Check that the rest of argQ is present
filter GetSize(port(ff, \Q)) >= offset + GetSize(argQ)
filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
filter clock == SigBit() || port(ff, \CLK) == clock
set ffoffset offset
endmatch
code argQ argD
SigSpec Q = port(ff, \Q);
dff = ff;
dffclock = port(ff, \CLK);
dffD = argQ;
argD = port(ff, \D);
argQ = Q;
dffD.replace(argQ, argD);
// Only search for ffrstmux if dffD only
// has two (ff, ffrstmux) users
if (nusers(dffD) > 2)
argD = SigSpec();
endcode
// (2) Match for a $mux cell implementing synchronous reset semantics ---
// exclusively drives the 'D' input of the $dff, with one of the $mux
// inputs being fully zero
match ffrstmux
if !argD.empty()
select ffrstmux->type.in($mux)
index <SigSpec> port(ffrstmux, \Y) === argD
choice <IdString> BA {\B, \A}
// DSP48E1 only supports reset to zero
select port(ffrstmux, BA).is_fully_zero()
define <bool> pol (BA == \B)
set ffrstpol pol
semioptional
endmatch
code argD
if (ffrstmux) {
dffrstmux = ffrstmux;
dffrstpol = ffrstpol;
argD = port(ffrstmux, ffrstpol ? \A : \B);
dffD.replace(port(ffrstmux, \Y), argD);
// Only search for ffcemux if argQ has at
// least 3 users (ff, <upstream>, ffrstmux) and
// dffD only has two (ff, ffrstmux)
if (!(nusers(argQ) >= 3 && nusers(dffD) == 2))
argD = SigSpec();
}
else
dffrstmux = nullptr;
endcode
// (3) Match for a $mux cell implement clock enable semantics --- one that
// exclusively drives the 'D' input of the $dff (or the other input of
// the reset $mux) and where one of this $mux's inputs is connected to
// the 'Q' output of the $dff
match ffcemux
if !argD.empty()
select ffcemux->type.in($mux)
index <SigSpec> port(ffcemux, \Y) === argD
choice <IdString> AB {\A, \B}
index <SigSpec> port(ffcemux, AB) === argQ
define <bool> pol (AB == \A)
set ffcepol pol
semioptional
endmatch
code argD
if (ffcemux) {
dffcemux = ffcemux;
dffcepol = ffcepol;
argD = port(ffcemux, ffcepol ? \B : \A);
dffD.replace(port(ffcemux, \Y), argD);
}
else
dffcemux = nullptr;
endcode

View File

@ -0,0 +1,427 @@
// This file describes the third of three pattern matcher setups that
// forms the `xilinx_dsp` pass described in xilinx_dsp.cc
// At a high level, it works as follows:
// (1) Starting from a DSP48E1 cell that (a) has the Z multiplexer
// (controlled by OPMODE[6:4]) set to zero and (b) doesn't already
// use the 'PCOUT' port
// (2.1) Match another DSP48E1 cell that (a) does not have the CREG enabled,
// (b) has its Z multiplexer output set to the 'C' port, which is
// driven by the 'P' output of the previous DSP cell, and (c) has its
// 'PCIN' port unused
// (2.2) Same as (2.1) but with the 'C' port driven by the 'P' output of the
// previous DSP cell right-shifted by 17 bits
// (3) For this subequent DSP48E1 match (i.e. PCOUT -> PCIN cascade exists)
// if (a) the previous DSP48E1 uses either the A2REG or A1REG, (b) this
// DSP48 does not use A2REG nor A1REG, (c) this DSP48E1 does not already
// have an ACOUT -> ACIN cascade, (d) the previous DSP does not already
// use its ACOUT port, then examine if an ACOUT -> ACIN cascade
// opportunity exists by matching for a $dff-with-optional-clock-enable-
// or-reset and checking that the 'D' input of this register is the same
// as the 'A' input of the previous DSP
// (4) Same as (3) but for BCOUT -> BCIN cascade
// (5) Recursively go to (2.1) until no more matches possible, keeping track
// of the longest possible chain found
// (6) The longest chain is then divided into chunks of no more than
// MAX_DSP_CASCADE in length (to prevent long cascades that exceed the
// height of a DSP column) with each DSP in each chunk being rewritten
// to use [ABP]COUT -> [ABP]CIN cascading as appropriate
// Notes:
// - Currently, [AB]COUT -> [AB]COUT cascades (3 or 4) are only considered
// if a PCOUT -> PCIN cascade is (2.1 or 2.2) first identified; this need
// not be the case --- [AB] cascades can exist independently of a P cascade
// (though all three cascades must come from the same DSP). This situation
// is not handled currently.
// - In addition, [AB]COUT -> [AB]COUT cascades (3 or 4) are currently
// conservative in that they examine the situation where (a) the previous
// DSP has [AB]2REG or [AB]1REG enabled, (b) that the downstream DSP has no
// registers enabled, and (c) that there exists only one additional register
// between the upstream and downstream DSPs. This can certainly be relaxed
// to identify situations ranging from (i) neither DSP uses any registers,
// to (ii) upstream DSP has 2 registers, downstream DSP has 2 registers, and
// there exists a further 2 registers between them. This remains a TODO
// item.
pattern xilinx_dsp_cascade
udata <std::function<SigSpec(const SigSpec&)>> unextend
udata <vector<std::tuple<Cell*,int,int,int>>> chain longest_chain
state <Cell*> next
state <SigSpec> clock
state <int> AREG BREG
// Variables used for subpatterns
state <SigSpec> argQ argD
state <bool> ffcepol ffrstpol
state <int> ffoffset
udata <SigSpec> dffD dffQ
udata <SigBit> dffclock
udata <Cell*> dff dffcemux dffrstmux
udata <bool> dffcepol dffrstpol
code
#define MAX_DSP_CASCADE 20
endcode
// (1) Starting from a DSP48E1 cell that (a) has the Z multiplexer
// (controlled by OPMODE[6:4]) set to zero and (b) doesn't already
// use the 'PCOUT' port
match first
select first->type.in(\DSP48E1)
select port(first, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("000")
select nusers(port(first, \PCOUT, SigSpec())) <= 1
endmatch
// (6) The longest chain is then divided into chunks of no more than
// MAX_DSP_CASCADE in length (to prevent long cascades that exceed the
// height of a DSP column) with each DSP in each chunk being rewritten
// to use [ABP]COUT -> [ABP]CIN cascading as appropriate
code
longest_chain.clear();
chain.emplace_back(first, -1, -1, -1);
subpattern(tail);
finally
chain.pop_back();
log_assert(chain.empty());
if (GetSize(longest_chain) > 1) {
Cell *dsp = std::get<0>(longest_chain.front());
Cell *dsp_pcin;
int P, AREG, BREG;
for (int i = 1; i < GetSize(longest_chain); i++) {
std::tie(dsp_pcin,P,AREG,BREG) = longest_chain[i];
if (i % MAX_DSP_CASCADE > 0) {
if (P >= 0) {
Wire *cascade = module->addWire(NEW_ID, 48);
dsp_pcin->setPort(ID(C), Const(0, 48));
dsp_pcin->setPort(ID(PCIN), cascade);
dsp->setPort(ID(PCOUT), cascade);
add_siguser(cascade, dsp_pcin);
add_siguser(cascade, dsp);
SigSpec opmode = port(dsp_pcin, \OPMODE, Const(0, 7));
if (P == 17)
opmode[6] = State::S1;
else if (P == 0)
opmode[6] = State::S0;
else log_abort();
opmode[5] = State::S0;
opmode[4] = State::S1;
dsp_pcin->setPort(\OPMODE, opmode);
log_debug("PCOUT -> PCIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
}
if (AREG >= 0) {
Wire *cascade = module->addWire(NEW_ID, 30);
dsp_pcin->setPort(ID(A), Const(0, 30));
dsp_pcin->setPort(ID(ACIN), cascade);
dsp->setPort(ID(ACOUT), cascade);
add_siguser(cascade, dsp_pcin);
add_siguser(cascade, dsp);
dsp->setParam(ID(ACASCREG), AREG);
dsp_pcin->setParam(ID(A_INPUT), Const("CASCADE"));
log_debug("ACOUT -> ACIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
}
if (BREG >= 0) {
Wire *cascade = module->addWire(NEW_ID, 18);
dsp_pcin->setPort(ID(B), Const(0, 18));
dsp_pcin->setPort(ID(BCIN), cascade);
dsp->setPort(ID(BCOUT), cascade);
add_siguser(cascade, dsp_pcin);
add_siguser(cascade, dsp);
dsp->setParam(ID(BCASCREG), BREG);
dsp_pcin->setParam(ID(B_INPUT), Const("CASCADE"));
log_debug("BCOUT -> BCIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin));
}
}
else {
log_debug(" Blocking %s -> %s cascade (exceeds max: %d)\n", log_id(dsp), log_id(dsp_pcin), MAX_DSP_CASCADE);
}
dsp = dsp_pcin;
}
accept;
}
endcode
// ------------------------------------------------------------------
subpattern tail
arg first
arg next
// (2.1) Match another DSP48E1 cell that (a) does not have the CREG enabled,
// (b) has its Z multiplexer output set to the 'C' port, which is
// driven by the 'P' output of the previous DSP cell, and (c) has its
// 'PCIN' port unused
match nextP
select nextP->type.in(\DSP48E1)
select !param(nextP, \CREG, State::S1).as_bool()
select port(nextP, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("011")
select nusers(port(nextP, \C, SigSpec())) > 1
select nusers(port(nextP, \PCIN, SigSpec())) == 0
index <SigBit> port(nextP, \C)[0] === port(std::get<0>(chain.back()), \P)[0]
semioptional
endmatch
// (2.2) Same as (2.1) but with the 'C' port driven by the 'P' output of the
// previous DSP cell right-shifted by 17 bits
match nextP_shift17
if !nextP
select nextP_shift17->type.in(\DSP48E1)
select !param(nextP_shift17, \CREG, State::S1).as_bool()
select port(nextP_shift17, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("011")
select nusers(port(nextP_shift17, \C, SigSpec())) > 1
select nusers(port(nextP_shift17, \PCIN, SigSpec())) == 0
index <SigBit> port(nextP_shift17, \C)[0] === port(std::get<0>(chain.back()), \P)[17]
semioptional
endmatch
code next
next = nextP;
if (!nextP)
next = nextP_shift17;
if (next) {
unextend = [](const SigSpec &sig) {
int i;
for (i = GetSize(sig)-1; i > 0; i--)
if (sig[i] != sig[i-1])
break;
// Do not remove non-const sign bit
if (sig[i].wire)
++i;
return sig.extract(0, i);
};
}
endcode
// (3) For this subequent DSP48E1 match (i.e. PCOUT -> PCIN cascade exists)
// if (a) the previous DSP48E1 uses either the A2REG or A1REG, (b) this
// DSP48 does not use A2REG nor A1REG, (c) this DSP48E1 does not already
// have an ACOUT -> ACIN cascade, (d) the previous DSP does not already
// use its ACOUT port, then examine if an ACOUT -> ACIN cascade
// opportunity exists by matching for a $dff-with-optional-clock-enable-
// or-reset and checking that the 'D' input of this register is the same
// as the 'A' input of the previous DSP
code argQ clock AREG
AREG = -1;
if (next) {
Cell *prev = std::get<0>(chain.back());
if (param(prev, \AREG, 2).as_int() > 0 &&
param(next, \AREG, 2).as_int() > 0 &&
param(next, \A_INPUT, Const("DIRECT")).decode_string() == "DIRECT" &&
nusers(port(prev, \ACOUT, SigSpec())) <= 1) {
argQ = unextend(port(next, \A));
clock = port(prev, \CLK);
subpattern(in_dffe);
if (dff) {
if (!dffrstmux && port(prev, \RSTA, State::S0) != State::S0)
goto reject_AREG;
if (dffrstmux && port(dffrstmux, \S) != port(prev, \RSTA, State::S0))
goto reject_AREG;
if (!dffcemux && port(prev, \CEA2, State::S0) != State::S0)
goto reject_AREG;
if (dffcemux && port(dffcemux, \S) != port(prev, \CEA2, State::S0))
goto reject_AREG;
if (dffD == unextend(port(prev, \A)))
AREG = 1;
reject_AREG: ;
}
}
}
endcode
// (4) Same as (3) but for BCOUT -> BCIN cascade
code argQ clock BREG
BREG = -1;
if (next) {
Cell *prev = std::get<0>(chain.back());
if (param(prev, \BREG, 2).as_int() > 0 &&
param(next, \BREG, 2).as_int() > 0 &&
param(next, \B_INPUT, Const("DIRECT")).decode_string() == "DIRECT" &&
port(next, \BCIN, SigSpec()).is_fully_zero() &&
nusers(port(prev, \BCOUT, SigSpec())) <= 1) {
argQ = unextend(port(next, \B));
clock = port(prev, \CLK);
subpattern(in_dffe);
if (dff) {
if (!dffrstmux && port(prev, \RSTB, State::S0) != State::S0)
goto reject_BREG;
if (dffrstmux && port(dffrstmux, \S) != port(prev, \RSTB, State::S0))
goto reject_BREG;
if (!dffcemux && port(prev, \CEB2, State::S0) != State::S0)
goto reject_BREG;
if (dffcemux && port(dffcemux, \S) != port(prev, \CEB2, State::S0))
goto reject_BREG;
if (dffD == unextend(port(prev, \B)))
BREG = 1;
reject_BREG: ;
}
}
}
endcode
// (5) Recursively go to (2.1) until no more matches possible, recording the
// longest possible chain
code
if (next) {
chain.emplace_back(next, nextP_shift17 ? 17 : nextP ? 0 : -1, AREG, BREG);
SigSpec sigC = unextend(port(next, \C));
if (nextP_shift17) {
if (GetSize(sigC)+17 <= GetSize(port(std::get<0>(chain.back()), \P)) &&
port(std::get<0>(chain.back()), \P).extract(17, GetSize(sigC)) != sigC)
subpattern(tail);
}
else {
if (GetSize(sigC) <= GetSize(port(std::get<0>(chain.back()), \P)) &&
port(std::get<0>(chain.back()), \P).extract(0, GetSize(sigC)) != sigC)
subpattern(tail);
}
} else {
if (GetSize(chain) > GetSize(longest_chain))
longest_chain = chain;
}
finally
if (next)
chain.pop_back();
endcode
// #######################
// Subpattern for matching against input registers, based on knowledge of the
// 'Q' input. Typically, identifying registers with clock-enable and reset
// capability would be a task would be handled by other Yosys passes such as
// dff2dffe, but since DSP inference happens much before this, these patterns
// have to be manually identified.
// At a high level:
// (1) Starting from a $dff cell that (partially or fully) drives the given
// 'Q' argument
// (2) Match for a $mux cell implementing synchronous reset semantics ---
// one that exclusively drives the 'D' input of the $dff, with one of its
// $mux inputs being fully zero
// (3) Match for a $mux cell implement clock enable semantics --- one that
// exclusively drives the 'D' input of the $dff (or the other input of
// the reset $mux) and where one of this $mux's inputs is connected to
// the 'Q' output of the $dff
subpattern in_dffe
arg argD argQ clock
code
dff = nullptr;
for (const auto &c : argQ.chunks()) {
// Abandon matches when 'Q' is a constant
if (!c.wire)
reject;
// Abandon matches when 'Q' has the keep attribute set
if (c.wire->get_bool_attribute(\keep))
reject;
// Abandon matches when 'Q' has a non-zero init attribute set
// (not supported by DSP48E1)
Const init = c.wire->attributes.at(\init, Const());
for (auto b : init.extract(c.offset, c.width))
if (b != State::Sx && b != State::S0)
reject;
}
endcode
// (1) Starting from a $dff cell that (partially or fully) drives the given
// 'Q' argument
match ff
select ff->type.in($dff)
// DSP48E1 does not support clock inversion
select param(ff, \CLK_POLARITY).as_bool()
slice offset GetSize(port(ff, \D))
index <SigBit> port(ff, \Q)[offset] === argQ[0]
// Check that the rest of argQ is present
filter GetSize(port(ff, \Q)) >= offset + GetSize(argQ)
filter port(ff, \Q).extract(offset, GetSize(argQ)) == argQ
filter clock == SigBit() || port(ff, \CLK) == clock
set ffoffset offset
endmatch
code argQ argD
SigSpec Q = port(ff, \Q);
dff = ff;
dffclock = port(ff, \CLK);
dffD = argQ;
argD = port(ff, \D);
argQ = Q;
dffD.replace(argQ, argD);
// Only search for ffrstmux if dffD only
// has two (ff, ffrstmux) users
if (nusers(dffD) > 2)
argD = SigSpec();
endcode
// (2) Match for a $mux cell implementing synchronous reset semantics ---
// exclusively drives the 'D' input of the $dff, with one of the $mux
// inputs being fully zero
match ffrstmux
if !argD.empty()
select ffrstmux->type.in($mux)
index <SigSpec> port(ffrstmux, \Y) === argD
choice <IdString> BA {\B, \A}
// DSP48E1 only supports reset to zero
select port(ffrstmux, BA).is_fully_zero()
define <bool> pol (BA == \B)
set ffrstpol pol
semioptional
endmatch
code argD
if (ffrstmux) {
dffrstmux = ffrstmux;
dffrstpol = ffrstpol;
argD = port(ffrstmux, ffrstpol ? \A : \B);
dffD.replace(port(ffrstmux, \Y), argD);
// Only search for ffcemux if argQ has at
// least 3 users (ff, <upstream>, ffrstmux) and
// dffD only has two (ff, ffrstmux)
if (!(nusers(argQ) >= 3 && nusers(dffD) == 2))
argD = SigSpec();
}
else
dffrstmux = nullptr;
endcode
// (3) Match for a $mux cell implement clock enable semantics --- one that
// exclusively drives the 'D' input of the $dff (or the other input of
// the reset $mux) and where one of this $mux's inputs is connected to
// the 'Q' output of the $dff
match ffcemux
if !argD.empty()
select ffcemux->type.in($mux)
index <SigSpec> port(ffcemux, \Y) === argD
choice <IdString> AB {\A, \B}
index <SigSpec> port(ffcemux, AB) === argQ
define <bool> pol (AB == \A)
set ffcepol pol
semioptional
endmatch
code argD
if (ffcemux) {
dffcemux = ffcemux;
dffcepol = ffcepol;
argD = port(ffcemux, ffcepol ? \B : \A);
dffD.replace(port(ffcemux, \Y), argD);
}
else
dffcemux = nullptr;
endcode

View File

@ -13,9 +13,9 @@ endcode
match first
select first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1)
select !first->has_keep_attr()
select !first->type.in(\FDRE) || !first->parameters.at(\IS_R_INVERTED, State::S0).as_bool()
select !first->type.in(\FDRE) || !first->parameters.at(\IS_D_INVERTED, State::S0).as_bool()
select !first->type.in(\FDRE, \FDRE_1) || first->connections_.at(\R, State::S0).is_fully_zero()
select !first->type.in(\FDRE) || !param(first, \IS_R_INVERTED, State::S0).as_bool()
select !first->type.in(\FDRE) || !param(first, \IS_D_INVERTED, State::S0).as_bool()
select !first->type.in(\FDRE, \FDRE_1) || port(first, \R, State::S0).is_fully_zero()
filter !non_first_cells.count(first)
generate
SigSpec C = module->addWire(NEW_ID);
@ -84,9 +84,9 @@ arg en_port
match first
select first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1)
select !first->has_keep_attr()
select !first->type.in(\FDRE) || !first->parameters.at(\IS_R_INVERTED, State::S0).as_bool()
select !first->type.in(\FDRE) || !first->parameters.at(\IS_D_INVERTED, State::S0).as_bool()
select !first->type.in(\FDRE, \FDRE_1) || first->connections_.at(\R, State::S0).is_fully_zero()
select !first->type.in(\FDRE) || !param(first, \IS_R_INVERTED, State::S0).as_bool()
select !first->type.in(\FDRE) || !param(first, \IS_D_INVERTED, State::S0).as_bool()
select !first->type.in(\FDRE, \FDRE_1) || port(first, \R, State::S0).is_fully_zero()
endmatch
code clk_port en_port
@ -111,10 +111,10 @@ match next
index <SigBit> port(next, \Q) === port(first, \D)
filter port(next, clk_port) == port(first, clk_port)
filter en_port == IdString() || port(next, en_port) == port(first, en_port)
filter !first->type.in(\FDRE) || next->parameters.at(\IS_C_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_C_INVERTED, State::S0).as_bool()
filter !first->type.in(\FDRE) || next->parameters.at(\IS_D_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_D_INVERTED, State::S0).as_bool()
filter !first->type.in(\FDRE) || next->parameters.at(\IS_R_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_R_INVERTED, State::S0).as_bool()
filter !first->type.in(\FDRE, \FDRE_1) || next->connections_.at(\R, State::S0).is_fully_zero()
filter !first->type.in(\FDRE) || param(next, \IS_C_INVERTED, State::S0).as_bool() == param(first, \IS_C_INVERTED, State::S0).as_bool()
filter !first->type.in(\FDRE) || param(next, \IS_D_INVERTED, State::S0).as_bool() == param(first, \IS_D_INVERTED, State::S0).as_bool()
filter !first->type.in(\FDRE) || param(next, \IS_R_INVERTED, State::S0).as_bool() == param(first, \IS_R_INVERTED, State::S0).as_bool()
filter !first->type.in(\FDRE, \FDRE_1) || port(next, \R, State::S0).is_fully_zero()
endmatch
code
@ -138,10 +138,10 @@ match next
index <SigBit> port(next, \Q) === port(chain.back(), \D)
filter port(next, clk_port) == port(first, clk_port)
filter en_port == IdString() || port(next, en_port) == port(first, en_port)
filter !first->type.in(\FDRE) || next->parameters.at(\IS_C_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_C_INVERTED, State::S0).as_bool()
filter !first->type.in(\FDRE) || next->parameters.at(\IS_D_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_D_INVERTED, State::S0).as_bool()
filter !first->type.in(\FDRE) || next->parameters.at(\IS_R_INVERTED, State::S0).as_bool() == first->parameters.at(\IS_R_INVERTED, State::S0).as_bool()
filter !first->type.in(\FDRE, \FDRE_1) || next->connections_.at(\R, State::S0).is_fully_zero()
filter !first->type.in(\FDRE) || param(next, \IS_C_INVERTED, State::S0).as_bool() == param(first, \IS_C_INVERTED, State::S0).as_bool()
filter !first->type.in(\FDRE) || param(next, \IS_D_INVERTED, State::S0).as_bool() == param(first, \IS_D_INVERTED, State::S0).as_bool()
filter !first->type.in(\FDRE) || param(next, \IS_R_INVERTED, State::S0).as_bool() == param(first, \IS_R_INVERTED, State::S0).as_bool()
filter !first->type.in(\FDRE, \FDRE_1) || port(next, \R, State::S0).is_fully_zero()
generate
Cell *cell = module->addCell(NEW_ID, chain.back()->type);
cell->setPort(\C, chain.back()->getPort(\C));
@ -149,7 +149,7 @@ generate
cell->setPort(\Q, chain.back()->getPort(\D));
if (cell->type == \FDRE) {
if (rng(2) == 0)
cell->setPort(\R, chain.back()->connections_.at(\R, State::S0));
cell->setPort(\R, port(chain.back(), \R, State::S0));
cell->setPort(\CE, chain.back()->getPort(\CE));
}
else if (cell->type.begins_with("$_DFFE_"))

View File

@ -349,6 +349,10 @@ void proc_dlatch(proc_dlatch_db_t &db, RTLIL::Process *proc)
continue;
}
if (proc->get_bool_attribute(ID(always_ff)))
log_error("Found non edge/level sensitive event in always_ff process `%s.%s'.\n",
db.module->name.c_str(), proc->name.c_str());
for (auto ss : sr->actions)
{
db.sigmap.apply(ss.first);
@ -383,6 +387,10 @@ void proc_dlatch(proc_dlatch_db_t &db, RTLIL::Process *proc)
int offset = 0;
for (auto chunk : nolatches_bits.first.chunks()) {
SigSpec lhs = chunk, rhs = nolatches_bits.second.extract(offset, chunk.width);
if (proc->get_bool_attribute(ID(always_latch)))
log_error("No latch inferred for signal `%s.%s' from always_latch process `%s.%s'.\n",
db.module->name.c_str(), log_signal(lhs), db.module->name.c_str(), proc->name.c_str());
else
log("No latch inferred for signal `%s.%s' from process `%s.%s'.\n",
db.module->name.c_str(), log_signal(lhs), db.module->name.c_str(), proc->name.c_str());
db.module->connect(lhs, rhs);
@ -410,6 +418,10 @@ void proc_dlatch(proc_dlatch_db_t &db, RTLIL::Process *proc)
cell->set_src_attribute(src);
db.generated_dlatches.insert(cell);
if (proc->get_bool_attribute(ID(always_comb)))
log_error("Latch inferred for signal `%s.%s' from always_comb process `%s.%s'.\n",
db.module->name.c_str(), log_signal(lhs), db.module->name.c_str(), proc->name.c_str());
else
log("Latch inferred for signal `%s.%s' from process `%s.%s': %s\n",
db.module->name.c_str(), log_signal(lhs), db.module->name.c_str(), proc->name.c_str(), log_id(cell));
}

View File

@ -198,6 +198,7 @@ struct Async2syncPass : public Pass {
module->addMux(NEW_ID, sig_d, new_q, sig_en, sig_q);
}
cell->setPort("\\D", sig_q);
cell->setPort("\\Q", new_q);
cell->unsetPort("\\EN");
cell->unsetParam("\\EN_POLARITY");

View File

@ -71,21 +71,21 @@ RTLIL::Module *module;
bool clk_polarity, en_polarity;
RTLIL::SigSpec clk_sig, en_sig;
inline std::string remap_name(RTLIL::IdString abc_name)
inline std::string remap_name(RTLIL::IdString abc9_name)
{
return stringf("$abc$%d$%s", map_autoidx, abc_name.c_str()+1);
return stringf("$abc$%d$%s", map_autoidx, abc9_name.c_str()+1);
}
void handle_loops(RTLIL::Design *design)
{
Pass::call(design, "scc -set_attr abc_scc_id {}");
Pass::call(design, "scc -set_attr abc9_scc_id {}");
// For every unique SCC found, (arbitrarily) find the first
// cell in the component, and select (and mark) all its output
// wires
pool<RTLIL::Const> ids_seen;
for (auto cell : module->cells()) {
auto it = cell->attributes.find(ID(abc_scc_id));
auto it = cell->attributes.find(ID(abc9_scc_id));
if (it != cell->attributes.end()) {
auto r = ids_seen.insert(it->second);
if (r.second) {
@ -105,7 +105,7 @@ void handle_loops(RTLIL::Design *design)
log_assert(w->port_input);
log_assert(b.offset < GetSize(w));
}
w->set_bool_attribute(ID(abc_scc_break));
w->set_bool_attribute(ID(abc9_scc_break));
module->swap_names(b.wire, w);
c.second = RTLIL::SigBit(w, b.offset);
}
@ -118,7 +118,7 @@ void handle_loops(RTLIL::Design *design)
module->fixup_ports();
}
std::string add_echos_to_abc_cmd(std::string str)
std::string add_echos_to_abc9_cmd(std::string str)
{
std::string new_str, token;
for (size_t i = 0; i < str.size(); i++) {
@ -140,7 +140,7 @@ std::string add_echos_to_abc_cmd(std::string str)
return new_str;
}
std::string fold_abc_cmd(std::string str)
std::string fold_abc9_cmd(std::string str)
{
std::string token, new_str = " ";
int char_counter = 10;
@ -184,7 +184,7 @@ std::string replace_tempdir(std::string text, std::string tempdir_name, bool sho
return text;
}
struct abc_output_filter
struct abc9_output_filter
{
bool got_cr;
int escape_seq_state;
@ -192,7 +192,7 @@ struct abc_output_filter
std::string tempdir_name;
bool show_tempdir;
abc_output_filter(std::string tempdir_name, bool show_tempdir) : tempdir_name(tempdir_name), show_tempdir(show_tempdir)
abc9_output_filter(std::string tempdir_name, bool show_tempdir) : tempdir_name(tempdir_name), show_tempdir(show_tempdir)
{
got_cr = false;
escape_seq_state = 0;
@ -247,7 +247,7 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri
bool cleanup, vector<int> lut_costs, bool dff_mode, std::string clk_str,
bool /*keepff*/, std::string delay_target, std::string /*lutin_shared*/, bool fast_mode,
bool show_tempdir, std::string box_file, std::string lut_file,
std::string wire_delay, const dict<int,IdString> &box_lookup
std::string wire_delay, const dict<int,IdString> &box_lookup, bool nomfs
)
{
module = current_module;
@ -293,68 +293,72 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri
log_header(design, "Extracting gate netlist of module `%s' to `%s/input.xaig'..\n",
module->name.c_str(), replace_tempdir(tempdir_name, tempdir_name, show_tempdir).c_str());
std::string abc_script;
std::string abc9_script;
if (!lut_costs.empty()) {
abc_script += stringf("read_lut %s/lutdefs.txt; ", tempdir_name.c_str());
abc9_script += stringf("read_lut %s/lutdefs.txt; ", tempdir_name.c_str());
if (!box_file.empty())
abc_script += stringf("read_box -v %s; ", box_file.c_str());
abc9_script += stringf("read_box -v %s; ", box_file.c_str());
}
else
if (!lut_file.empty()) {
abc_script += stringf("read_lut %s; ", lut_file.c_str());
abc9_script += stringf("read_lut %s; ", lut_file.c_str());
if (!box_file.empty())
abc_script += stringf("read_box -v %s; ", box_file.c_str());
abc9_script += stringf("read_box -v %s; ", box_file.c_str());
}
else
log_abort();
abc_script += stringf("&read %s/input.xaig; &ps; ", tempdir_name.c_str());
abc9_script += stringf("&read %s/input.xaig; &ps; ", tempdir_name.c_str());
if (!script_file.empty()) {
if (script_file[0] == '+') {
for (size_t i = 1; i < script_file.size(); i++)
if (script_file[i] == '\'')
abc_script += "'\\''";
abc9_script += "'\\''";
else if (script_file[i] == ',')
abc_script += " ";
abc9_script += " ";
else
abc_script += script_file[i];
abc9_script += script_file[i];
} else
abc_script += stringf("source %s", script_file.c_str());
abc9_script += stringf("source %s", script_file.c_str());
} else if (!lut_costs.empty() || !lut_file.empty()) {
//bool all_luts_cost_same = true;
//for (int this_cost : lut_costs)
// if (this_cost != lut_costs.front())
// all_luts_cost_same = false;
abc_script += fast_mode ? ABC_FAST_COMMAND_LUT : ABC_COMMAND_LUT;
abc9_script += fast_mode ? ABC_FAST_COMMAND_LUT : ABC_COMMAND_LUT;
//if (all_luts_cost_same && !fast_mode)
// abc_script += "; lutpack {S}";
// abc9_script += "; lutpack {S}";
} else
log_abort();
//if (script_file.empty() && !delay_target.empty())
// for (size_t pos = abc_script.find("dretime;"); pos != std::string::npos; pos = abc_script.find("dretime;", pos+1))
// abc_script = abc_script.substr(0, pos) + "dretime; retime -o {D};" + abc_script.substr(pos+8);
// for (size_t pos = abc9_script.find("dretime;"); pos != std::string::npos; pos = abc9_script.find("dretime;", pos+1))
// abc9_script = abc9_script.substr(0, pos) + "dretime; retime -o {D};" + abc9_script.substr(pos+8);
for (size_t pos = abc_script.find("{D}"); pos != std::string::npos; pos = abc_script.find("{D}", pos))
abc_script = abc_script.substr(0, pos) + delay_target + abc_script.substr(pos+3);
for (size_t pos = abc9_script.find("{D}"); pos != std::string::npos; pos = abc9_script.find("{D}", pos))
abc9_script = abc9_script.substr(0, pos) + delay_target + abc9_script.substr(pos+3);
//for (size_t pos = abc_script.find("{S}"); pos != std::string::npos; pos = abc_script.find("{S}", pos))
// abc_script = abc_script.substr(0, pos) + lutin_shared + abc_script.substr(pos+3);
//for (size_t pos = abc9_script.find("{S}"); pos != std::string::npos; pos = abc9_script.find("{S}", pos))
// abc9_script = abc9_script.substr(0, pos) + lutin_shared + abc9_script.substr(pos+3);
for (size_t pos = abc_script.find("{W}"); pos != std::string::npos; pos = abc_script.find("{W}", pos))
abc_script = abc_script.substr(0, pos) + wire_delay + abc_script.substr(pos+3);
for (size_t pos = abc9_script.find("{W}"); pos != std::string::npos; pos = abc9_script.find("{W}", pos))
abc9_script = abc9_script.substr(0, pos) + wire_delay + abc9_script.substr(pos+3);
abc_script += stringf("; &write %s/output.aig", tempdir_name.c_str());
abc_script = add_echos_to_abc_cmd(abc_script);
if (nomfs)
for (size_t pos = abc9_script.find("&mfs"); pos != std::string::npos; pos = abc9_script.find("&mfs", pos))
abc9_script = abc9_script.erase(pos, strlen("&mfs"));
for (size_t i = 0; i+1 < abc_script.size(); i++)
if (abc_script[i] == ';' && abc_script[i+1] == ' ')
abc_script[i+1] = '\n';
abc9_script += stringf("; &write %s/output.aig", tempdir_name.c_str());
abc9_script = add_echos_to_abc9_cmd(abc9_script);
for (size_t i = 0; i+1 < abc9_script.size(); i++)
if (abc9_script[i] == ';' && abc9_script[i+1] == ' ')
abc9_script[i+1] = '\n';
FILE *f = fopen(stringf("%s/abc.script", tempdir_name.c_str()).c_str(), "wt");
fprintf(f, "%s\n", abc_script.c_str());
fprintf(f, "%s\n", abc9_script.c_str());
fclose(f);
if (dff_mode || !clk_str.empty())
@ -420,7 +424,7 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri
// the expose operation -- remove them from PO/PI
// and re-connecting them back together
for (auto wire : module->wires()) {
auto it = wire->attributes.find(ID(abc_scc_break));
auto it = wire->attributes.find(ID(abc9_scc_break));
if (it != wire->attributes.end()) {
wire->attributes.erase(it);
log_assert(wire->port_output);
@ -450,28 +454,28 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri
log("Running ABC command: %s\n", replace_tempdir(buffer, tempdir_name, show_tempdir).c_str());
#ifndef YOSYS_LINK_ABC
abc_output_filter filt(tempdir_name, show_tempdir);
int ret = run_command(buffer, std::bind(&abc_output_filter::next_line, filt, std::placeholders::_1));
abc9_output_filter filt(tempdir_name, show_tempdir);
int ret = run_command(buffer, std::bind(&abc9_output_filter::next_line, filt, std::placeholders::_1));
#else
// These needs to be mutable, supposedly due to getopt
char *abc_argv[5];
char *abc9_argv[5];
string tmp_script_name = stringf("%s/abc.script", tempdir_name.c_str());
abc_argv[0] = strdup(exe_file.c_str());
abc_argv[1] = strdup("-s");
abc_argv[2] = strdup("-f");
abc_argv[3] = strdup(tmp_script_name.c_str());
abc_argv[4] = 0;
int ret = Abc_RealMain(4, abc_argv);
free(abc_argv[0]);
free(abc_argv[1]);
free(abc_argv[2]);
free(abc_argv[3]);
abc9_argv[0] = strdup(exe_file.c_str());
abc9_argv[1] = strdup("-s");
abc9_argv[2] = strdup("-f");
abc9_argv[3] = strdup(tmp_script_name.c_str());
abc9_argv[4] = 0;
int ret = Abc_RealMain(4, abc9_argv);
free(abc9_argv[0]);
free(abc9_argv[1]);
free(abc9_argv[2]);
free(abc9_argv[3]);
#endif
if (ret != 0)
log_error("ABC: execution of command \"%s\" failed: return code %d.\n", buffer.c_str(), ret);
buffer = stringf("%s/%s", tempdir_name.c_str(), "output.aig");
ifs.open(buffer);
ifs.open(buffer, std::ifstream::binary);
if (ifs.fail())
log_error("Can't open ABC output file `%s'.\n", buffer.c_str());
@ -513,7 +517,7 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri
signal = std::move(bits);
}
dict<IdString, bool> abc_box;
dict<IdString, bool> abc9_box;
vector<RTLIL::Cell*> boxes;
for (const auto &it : module->cells_) {
auto cell = it.second;
@ -521,10 +525,10 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri
module->remove(cell);
continue;
}
auto jt = abc_box.find(cell->type);
if (jt == abc_box.end()) {
auto jt = abc9_box.find(cell->type);
if (jt == abc9_box.end()) {
RTLIL::Module* box_module = design->module(cell->type);
jt = abc_box.insert(std::make_pair(cell->type, box_module && box_module->attributes.count(ID(abc_box_id)))).first;
jt = abc9_box.insert(std::make_pair(cell->type, box_module && box_module->attributes.count(ID(abc9_box_id)))).first;
}
if (jt->second)
boxes.emplace_back(cell);
@ -606,7 +610,6 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri
existing_cell = module->cell(c->name);
log_assert(existing_cell);
cell = module->addCell(remap_name(c->name), c->type);
module->swap_names(cell, existing_cell);
}
if (markgroups) cell->attributes[ID(abcgroup)] = map_autoidx;
@ -642,8 +645,22 @@ void abc9_module(RTLIL::Design *design, RTLIL::Module *current_module, std::stri
}
}
for (auto cell : boxes)
module->remove(cell);
for (auto existing_cell : boxes) {
Cell *cell = module->cell(remap_name(existing_cell->name));
if (cell) {
for (auto &conn : existing_cell->connections()) {
if (!conn.second.is_wire())
continue;
Wire *wire = conn.second.as_wire();
if (!wire->get_bool_attribute(ID(abc9_padding)))
continue;
cell->unsetPort(conn.first);
log_debug("Dropping padded port connection for %s (%s) .%s (%s )\n", log_id(cell), cell->type.c_str(), log_id(conn.first), log_signal(conn.second));
}
module->swap_names(cell, existing_cell);
}
module->remove(existing_cell);
}
// Copy connections (and rename) from mapped_mod to module
for (auto conn : mapped_mod->connections()) {
@ -814,17 +831,17 @@ struct Abc9Pass : public Pass {
log(" if no -script parameter is given, the following scripts are used:\n");
log("\n");
log(" for -lut/-luts (only one LUT size):\n");
log("%s\n", fold_abc_cmd(ABC_COMMAND_LUT /*"; lutpack {S}"*/).c_str());
log("%s\n", fold_abc9_cmd(ABC_COMMAND_LUT /*"; lutpack {S}"*/).c_str());
log("\n");
log(" for -lut/-luts (different LUT sizes):\n");
log("%s\n", fold_abc_cmd(ABC_COMMAND_LUT).c_str());
log("%s\n", fold_abc9_cmd(ABC_COMMAND_LUT).c_str());
log("\n");
log(" -fast\n");
log(" use different default scripts that are slightly faster (at the cost\n");
log(" of output quality):\n");
log("\n");
log(" for -lut/-luts:\n");
log("%s\n", fold_abc_cmd(ABC_FAST_COMMAND_LUT).c_str());
log("%s\n", fold_abc9_cmd(ABC_FAST_COMMAND_LUT).c_str());
log("\n");
log(" -D <picoseconds>\n");
log(" set delay target. the string {D} in the default scripts above is\n");
@ -908,6 +925,7 @@ struct Abc9Pass : public Pass {
std::string delay_target, lutin_shared = "-S 1", wire_delay;
bool fast_mode = false, dff_mode = false, keepff = false, cleanup = true;
bool show_tempdir = false;
bool nomfs = false;
vector<int> lut_costs;
markgroups = false;
@ -1030,6 +1048,10 @@ struct Abc9Pass : public Pass {
wire_delay = "-W " + args[++argidx];
continue;
}
if (arg == "-nomfs") {
nomfs = true;
continue;
}
break;
}
extra_args(args, argidx, design);
@ -1044,7 +1066,7 @@ struct Abc9Pass : public Pass {
dict<int,IdString> box_lookup;
for (auto m : design->modules()) {
auto it = m->attributes.find(ID(abc_box_id));
auto it = m->attributes.find(ID(abc9_box_id));
if (it == m->attributes.end())
continue;
if (m->name.begins_with("$paramod"))
@ -1052,7 +1074,7 @@ struct Abc9Pass : public Pass {
auto id = it->second.as_int();
auto r = box_lookup.insert(std::make_pair(id, m->name));
if (!r.second)
log_error("Module '%s' has the same abc_box_id = %d value as '%s'.\n",
log_error("Module '%s' has the same abc9_box_id = %d value as '%s'.\n",
log_id(m), id, log_id(r.first->second));
log_assert(r.second);
@ -1060,24 +1082,24 @@ struct Abc9Pass : public Pass {
for (auto p : m->ports) {
auto w = m->wire(p);
log_assert(w);
if (w->attributes.count(ID(abc_carry))) {
if (w->attributes.count(ID(abc9_carry))) {
if (w->port_input) {
if (carry_in)
log_error("Module '%s' contains more than one 'abc_carry' input port.\n", log_id(m));
log_error("Module '%s' contains more than one 'abc9_carry' input port.\n", log_id(m));
carry_in = w;
}
else if (w->port_output) {
if (carry_out)
log_error("Module '%s' contains more than one 'abc_carry' input port.\n", log_id(m));
log_error("Module '%s' contains more than one 'abc9_carry' input port.\n", log_id(m));
carry_out = w;
}
}
}
if (carry_in || carry_out) {
if (carry_in && !carry_out)
log_error("Module '%s' contains an 'abc_carry' input port but no output port.\n", log_id(m));
log_error("Module '%s' contains an 'abc9_carry' input port but no output port.\n", log_id(m));
if (!carry_in && carry_out)
log_error("Module '%s' contains an 'abc_carry' output port but no input port.\n", log_id(m));
log_error("Module '%s' contains an 'abc9_carry' output port but no input port.\n", log_id(m));
// Make carry_in the last PI, and carry_out the last PO
// since ABC requires it this way
auto &ports = m->ports;
@ -1105,7 +1127,7 @@ struct Abc9Pass : public Pass {
for (auto mod : design->selected_modules())
{
if (mod->attributes.count(ID(abc_box_id)))
if (mod->attributes.count(ID(abc9_box_id)))
continue;
if (mod->processes.size() > 0) {
@ -1118,7 +1140,7 @@ struct Abc9Pass : public Pass {
if (!dff_mode || !clk_str.empty()) {
abc9_module(design, mod, script_file, exe_file, cleanup, lut_costs, dff_mode, clk_str, keepff,
delay_target, lutin_shared, fast_mode, show_tempdir,
box_file, lut_file, wire_delay, box_lookup);
box_file, lut_file, wire_delay, box_lookup, nomfs);
continue;
}
@ -1264,7 +1286,7 @@ struct Abc9Pass : public Pass {
en_sig = assign_map(std::get<3>(it.first));
abc9_module(design, mod, script_file, exe_file, cleanup, lut_costs, !clk_sig.empty(), "$",
keepff, delay_target, lutin_shared, fast_mode, show_tempdir,
box_file, lut_file, wire_delay, box_lookup);
box_file, lut_file, wire_delay, box_lookup, nomfs);
assign_map.set(mod);
}
}

View File

@ -27,6 +27,7 @@ struct AigmapPass : public Pass {
AigmapPass() : Pass("aigmap", "map logic to and-inverter-graph circuit") { }
void help() YS_OVERRIDE
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" aigmap [options] [selection]\n");
log("\n");
@ -36,10 +37,15 @@ struct AigmapPass : public Pass {
log(" -nand\n");
log(" Enable creation of $_NAND_ cells\n");
log("\n");
log(" -select\n");
log(" Overwrite replaced cells in the current selection with new $_AND_,\n");
log(" $_NOT_, and $_NAND_, cells\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
{
bool nand_mode = false;
bool nand_mode = false, select_mode = false;
log_header(design, "Executing AIGMAP pass (map logic to AIG).\n");
@ -50,6 +56,10 @@ struct AigmapPass : public Pass {
nand_mode = true;
continue;
}
if (args[argidx] == "-select") {
select_mode = true;
continue;
}
break;
}
extra_args(args, argidx, design);
@ -62,6 +72,7 @@ struct AigmapPass : public Pass {
dict<IdString, int> stat_not_replaced;
int orig_num_cells = GetSize(module->cells());
pool<IdString> new_sel;
for (auto cell : module->selected_cells())
{
Aig aig(cell);
@ -75,6 +86,8 @@ struct AigmapPass : public Pass {
if (aig.name.empty()) {
not_replaced_count++;
stat_not_replaced[cell->type]++;
if (select_mode)
new_sel.insert(cell->name);
continue;
}
@ -95,19 +108,33 @@ struct AigmapPass : public Pass {
SigBit A = sigs.at(node.left_parent);
SigBit B = sigs.at(node.right_parent);
if (nand_mode && node.inverter) {
bit = module->NandGate(NEW_ID, A, B);
bit = module->addWire(NEW_ID);
auto gate = module->addNandGate(NEW_ID, A, B, bit);
if (select_mode)
new_sel.insert(gate->name);
goto skip_inverter;
} else {
pair<int, int> key(node.left_parent, node.right_parent);
if (and_cache.count(key))
bit = and_cache.at(key);
else
bit = module->AndGate(NEW_ID, A, B);
else {
bit = module->addWire(NEW_ID);
auto gate = module->addAndGate(NEW_ID, A, B, bit);
if (select_mode)
new_sel.insert(gate->name);
}
}
}
if (node.inverter)
bit = module->NotGate(NEW_ID, bit);
if (node.inverter) {
SigBit new_bit = module->addWire(NEW_ID);
auto gate = module->addNotGate(NEW_ID, bit, new_bit);
bit = new_bit;
if (select_mode)
new_sel.insert(gate->name);
}
skip_inverter:
for (auto &op : node.outports)
@ -142,6 +169,13 @@ struct AigmapPass : public Pass {
for (auto cell : replaced_cells)
module->remove(cell);
if (select_mode) {
log_assert(!design->selection_stack.empty());
RTLIL::Selection& sel = design->selection_stack.back();
sel.selected_members[module->name] = std::move(new_sel);
}
}
}
} AigmapPass;

View File

@ -262,10 +262,14 @@ struct ExtractFaWorker
pool<SigBit> new_leaves = leaves;
new_leaves.erase(bit);
if (cell->hasPort(ID::A)) new_leaves.insert(sigmap(SigBit(cell->getPort(ID::A))));
if (cell->hasPort(ID::B)) new_leaves.insert(sigmap(SigBit(cell->getPort(ID::B))));
if (cell->hasPort(ID(C))) new_leaves.insert(sigmap(SigBit(cell->getPort(ID(C)))));
if (cell->hasPort(ID(D))) new_leaves.insert(sigmap(SigBit(cell->getPort(ID(D)))));
for (auto port : {ID::A, ID::B, ID(C), ID(D)}) {
if (!cell->hasPort(port))
continue;
auto bit = sigmap(SigBit(cell->getPort(port)));
if (!bit.wire)
continue;
new_leaves.insert(bit);
}
if (GetSize(new_leaves) > maxbreadth)
continue;

View File

@ -394,7 +394,7 @@ struct FlowGraph
pair<pool<RTLIL::SigBit>, pool<RTLIL::SigBit>> edge_cut()
{
pool<RTLIL::SigBit> x, xi;
pool<RTLIL::SigBit> x = {source}, xi; // X and X̅ in the paper
NodePrime source_prime = {source, true};
pool<NodePrime> visited;
@ -437,6 +437,7 @@ struct FlowGraph
for (auto collapsed_node : collapsed[sink])
xi.insert(collapsed_node);
log_assert(x[source] && !xi[source]);
log_assert(!x[sink] && xi[sink]);
return {x, xi};
}
@ -1050,7 +1051,7 @@ struct FlowmapWorker
auto cut_inputs = cut_lut_at_gate(lut, lut_gate);
pool<RTLIL::SigBit> gate_inputs = cut_inputs.first, other_inputs = cut_inputs.second;
if (gate_inputs.empty() && (int)other_inputs.size() == order)
if (gate_inputs.empty() && (int)other_inputs.size() >= order)
{
if (debug_relax)
log(" Breaking would result in a (k+1)-LUT.\n");

View File

@ -257,6 +257,12 @@ struct TechmapWorker
w->add_strpool_attribute(ID(src), extra_src_attrs);
}
design->select(module, w);
if (it.second->name.begins_with("\\_TECHMAP_REPLACE_.")) {
IdString replace_name = stringf("%s%s", orig_cell_name.c_str(), it.second->name.c_str() + strlen("\\_TECHMAP_REPLACE_"));
Wire *replace_w = module->addWire(replace_name, it.second);
module->connect(replace_w, w);
}
}
SigMap tpl_sigmap(tpl);
@ -378,6 +384,8 @@ struct TechmapWorker
if (techmap_replace_cell)
c_name = orig_cell_name;
else if (it.second->name.begins_with("\\_TECHMAP_REPLACE_."))
c_name = stringf("%s%s", orig_cell_name.c_str(), c_name.c_str() + strlen("\\_TECHMAP_REPLACE_"));
else
apply_prefix(cell->name, c_name);
@ -1198,6 +1206,12 @@ struct TechmapPass : public Pass {
log("\n");
log("A cell with the name _TECHMAP_REPLACE_ in the map file will inherit the name\n");
log("and attributes of the cell that is being replaced.\n");
log("A cell with a name of the form `_TECHMAP_REPLACE_.<suffix>` in the map file will\n");
log("be named thus but with the `_TECHMAP_REPLACE_' prefix substituted with the name\n");
log("of the cell being replaced.\n");
log("Similarly, a wire named in the form `_TECHMAP_REPLACE_.<suffix>` will cause a\n");
log("new wire alias to be created and named as above but with the `_TECHMAP_REPLACE_'\n");
log("prefix also substituted.\n");
log("\n");
log("See 'help extract' for a pass that does the opposite thing.\n");
log("\n");

View File

@ -351,6 +351,11 @@ struct TestAutotbBackend : public Backend {
log(" -n <int>\n");
log(" number of iterations the test bench should run (default = 1000)\n");
log("\n");
log(" -seed <int>\n");
log(" seed used for pseudo-random number generation (default = 0).\n");
log(" a value of 0 will cause an arbitrary seed to be chosen, based on\n");
log(" the current system time.\n");
log("\n");
}
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
{

View File

@ -9,12 +9,12 @@ GENFILES += techlibs/common/simcells_help.inc
techlibs/common/simlib_help.inc: techlibs/common/cellhelp.py techlibs/common/simlib.v
$(Q) mkdir -p techlibs/common
$(P) python3 $^ > $@.new
$(P) $(PYTHON_EXECUTABLE) $^ > $@.new
$(Q) mv $@.new $@
techlibs/common/simcells_help.inc: techlibs/common/cellhelp.py techlibs/common/simcells.v
$(Q) mkdir -p techlibs/common
$(P) python3 $^ > $@.new
$(P) $(PYTHON_EXECUTABLE) $^ > $@.new
$(Q) mv $@.new $@
kernel/register.o: techlibs/common/simlib_help.inc techlibs/common/simcells_help.inc
@ -28,4 +28,5 @@ $(eval $(call add_share_file,share,techlibs/common/dff2ff.v))
$(eval $(call add_share_file,share,techlibs/common/gate2lut.v))
$(eval $(call add_share_file,share,techlibs/common/cmp2lut.v))
$(eval $(call add_share_file,share,techlibs/common/cells.lib))
$(eval $(call add_share_file,share,techlibs/common/mul2dsp.v))
$(eval $(call add_share_file,share,techlibs/common/dummy.box))

View File

@ -7,7 +7,7 @@
// with n <= k inputs should be techmapped in this way, because this shortens the critical path
// from n to 1 by avoiding carry chains.
(* techmap_celltype = "$eq $ne $lt $le $gt $ge" *)
(* techmap_celltype = "$lt $le $gt $ge" *)
module _90_lut_cmp_ (A, B, Y);
parameter A_SIGNED = 0;

296
techlibs/common/mul2dsp.v Normal file
View File

@ -0,0 +1,296 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
* 2019 Eddie Hung <eddie@fpgeh.com>
* 2019 David Shah <dave@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* ---
*
* Tech-mapping rules for decomposing arbitrarily-sized $mul cells
* into an equivalent collection of smaller `DSP_NAME cells (with the
* same interface as $mul) no larger than `DSP_[AB]_MAXWIDTH, attached
* to $shl and $add cells.
*
*/
`ifndef DSP_A_MAXWIDTH
$fatal(1, "Macro DSP_A_MAXWIDTH must be defined");
`endif
`ifndef DSP_B_MAXWIDTH
$fatal(1, "Macro DSP_B_MAXWIDTH must be defined");
`endif
`ifndef DSP_B_MAXWIDTH
$fatal(1, "Macro DSP_B_MAXWIDTH must be defined");
`endif
`ifndef DSP_A_MAXWIDTH_PARTIAL
`define DSP_A_MAXWIDTH_PARTIAL `DSP_A_MAXWIDTH
`endif
`ifndef DSP_B_MAXWIDTH_PARTIAL
`define DSP_B_MAXWIDTH_PARTIAL `DSP_B_MAXWIDTH
`endif
`ifndef DSP_NAME
$fatal(1, "Macro DSP_NAME must be defined");
`endif
`define MAX(a,b) (a > b ? a : b)
`define MIN(a,b) (a < b ? a : b)
(* techmap_celltype = "$mul $__mul" *)
module _80_mul (A, B, Y);
parameter A_SIGNED = 0;
parameter B_SIGNED = 0;
parameter A_WIDTH = 1;
parameter B_WIDTH = 1;
parameter Y_WIDTH = 1;
input [A_WIDTH-1:0] A;
input [B_WIDTH-1:0] B;
output [Y_WIDTH-1:0] Y;
parameter _TECHMAP_CELLTYPE_ = "";
generate
if (0) begin end
`ifdef DSP_A_MINWIDTH
else if (A_WIDTH < `DSP_A_MINWIDTH)
wire _TECHMAP_FAIL_ = 1;
`endif
`ifdef DSP_B_MINWIDTH
else if (B_WIDTH < `DSP_B_MINWIDTH)
wire _TECHMAP_FAIL_ = 1;
`endif
`ifdef DSP_Y_MINWIDTH
else if (Y_WIDTH < `DSP_Y_MINWIDTH)
wire _TECHMAP_FAIL_ = 1;
`endif
`ifdef DSP_SIGNEDONLY
else if (_TECHMAP_CELLTYPE_ == "$mul" && !A_SIGNED && !B_SIGNED)
\$mul #(
.A_SIGNED(1),
.B_SIGNED(1),
.A_WIDTH(A_WIDTH + 1),
.B_WIDTH(B_WIDTH + 1),
.Y_WIDTH(Y_WIDTH)
) _TECHMAP_REPLACE_ (
.A({1'b0, A}),
.B({1'b0, B}),
.Y(Y)
);
`endif
else if (_TECHMAP_CELLTYPE_ == "$mul" && A_WIDTH < B_WIDTH)
\$mul #(
.A_SIGNED(B_SIGNED),
.B_SIGNED(A_SIGNED),
.A_WIDTH(B_WIDTH),
.B_WIDTH(A_WIDTH),
.Y_WIDTH(Y_WIDTH)
) _TECHMAP_REPLACE_ (
.A(B),
.B(A),
.Y(Y)
);
else begin
wire [1023:0] _TECHMAP_DO_ = "proc; clean";
`ifdef DSP_SIGNEDONLY
localparam sign_headroom = 1;
`else
localparam sign_headroom = 0;
`endif
genvar i;
if (A_WIDTH > `DSP_A_MAXWIDTH) begin
localparam n = (A_WIDTH-`DSP_A_MAXWIDTH+`DSP_A_MAXWIDTH_PARTIAL-sign_headroom-1) / (`DSP_A_MAXWIDTH_PARTIAL-sign_headroom);
localparam partial_Y_WIDTH = `MIN(Y_WIDTH, B_WIDTH+`DSP_A_MAXWIDTH_PARTIAL);
localparam last_A_WIDTH = A_WIDTH-n*(`DSP_A_MAXWIDTH_PARTIAL-sign_headroom);
localparam last_Y_WIDTH = B_WIDTH+last_A_WIDTH;
if (A_SIGNED && B_SIGNED) begin
wire signed [partial_Y_WIDTH-1:0] partial [n-1:0];
wire signed [last_Y_WIDTH-1:0] last_partial;
wire signed [Y_WIDTH-1:0] partial_sum [n:0];
end
else begin
wire [partial_Y_WIDTH-1:0] partial [n-1:0];
wire [last_Y_WIDTH-1:0] last_partial;
wire [Y_WIDTH-1:0] partial_sum [n:0];
end
for (i = 0; i < n; i=i+1) begin:sliceA
\$__mul #(
.A_SIGNED(sign_headroom),
.B_SIGNED(B_SIGNED),
.A_WIDTH(`DSP_A_MAXWIDTH_PARTIAL),
.B_WIDTH(B_WIDTH),
.Y_WIDTH(partial_Y_WIDTH)
) mul (
.A({{sign_headroom{1'b0}}, A[i*(`DSP_A_MAXWIDTH_PARTIAL-sign_headroom) +: `DSP_A_MAXWIDTH_PARTIAL-sign_headroom]}),
.B(B),
.Y(partial[i])
);
// TODO: Currently a 'cascade' approach to summing the partial
// products is taken here, but a more efficient 'binary
// reduction' approach also exists...
if (i == 0)
assign partial_sum[i] = partial[i];
else
assign partial_sum[i] = (partial[i] << (* mul2dsp *) i*(`DSP_A_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) partial_sum[i-1];
end
\$__mul #(
.A_SIGNED(A_SIGNED),
.B_SIGNED(B_SIGNED),
.A_WIDTH(last_A_WIDTH),
.B_WIDTH(B_WIDTH),
.Y_WIDTH(last_Y_WIDTH)
) sliceA.last (
.A(A[A_WIDTH-1 -: last_A_WIDTH]),
.B(B),
.Y(last_partial)
);
assign partial_sum[n] = (last_partial << (* mul2dsp *) n*(`DSP_A_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) partial_sum[n-1];
assign Y = partial_sum[n];
end
else if (B_WIDTH > `DSP_B_MAXWIDTH) begin
localparam n = (B_WIDTH-`DSP_B_MAXWIDTH+`DSP_B_MAXWIDTH_PARTIAL-sign_headroom-1) / (`DSP_B_MAXWIDTH_PARTIAL-sign_headroom);
localparam partial_Y_WIDTH = `MIN(Y_WIDTH, A_WIDTH+`DSP_B_MAXWIDTH_PARTIAL);
localparam last_B_WIDTH = B_WIDTH-n*(`DSP_B_MAXWIDTH_PARTIAL-sign_headroom);
localparam last_Y_WIDTH = A_WIDTH+last_B_WIDTH;
if (A_SIGNED && B_SIGNED) begin
wire signed [partial_Y_WIDTH-1:0] partial [n-1:0];
wire signed [last_Y_WIDTH-1:0] last_partial;
wire signed [Y_WIDTH-1:0] partial_sum [n:0];
end
else begin
wire [partial_Y_WIDTH-1:0] partial [n-1:0];
wire [last_Y_WIDTH-1:0] last_partial;
wire [Y_WIDTH-1:0] partial_sum [n:0];
end
for (i = 0; i < n; i=i+1) begin:sliceB
\$__mul #(
.A_SIGNED(A_SIGNED),
.B_SIGNED(sign_headroom),
.A_WIDTH(A_WIDTH),
.B_WIDTH(`DSP_B_MAXWIDTH_PARTIAL),
.Y_WIDTH(partial_Y_WIDTH)
) mul (
.A(A),
.B({{sign_headroom{1'b0}}, B[i*(`DSP_B_MAXWIDTH_PARTIAL-sign_headroom) +: `DSP_B_MAXWIDTH_PARTIAL-sign_headroom]}),
.Y(partial[i])
);
// TODO: Currently a 'cascade' approach to summing the partial
// products is taken here, but a more efficient 'binary
// reduction' approach also exists...
if (i == 0)
assign partial_sum[i] = partial[i];
else
assign partial_sum[i] = (partial[i] << (* mul2dsp *) i*(`DSP_B_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) partial_sum[i-1];
end
\$__mul #(
.A_SIGNED(A_SIGNED),
.B_SIGNED(B_SIGNED),
.A_WIDTH(A_WIDTH),
.B_WIDTH(last_B_WIDTH),
.Y_WIDTH(last_Y_WIDTH)
) mul_sliceB_last (
.A(A),
.B(B[B_WIDTH-1 -: last_B_WIDTH]),
.Y(last_partial)
);
assign partial_sum[n] = (last_partial << (* mul2dsp *) n*(`DSP_B_MAXWIDTH_PARTIAL-sign_headroom)) + (* mul2dsp *) partial_sum[n-1];
assign Y = partial_sum[n];
end
else begin
if (A_SIGNED)
wire signed [`DSP_A_MAXWIDTH-1:0] Aext = $signed(A);
else
wire [`DSP_A_MAXWIDTH-1:0] Aext = A;
if (B_SIGNED)
wire signed [`DSP_B_MAXWIDTH-1:0] Bext = $signed(B);
else
wire [`DSP_B_MAXWIDTH-1:0] Bext = B;
`DSP_NAME #(
.A_SIGNED(A_SIGNED),
.B_SIGNED(B_SIGNED),
.A_WIDTH(`DSP_A_MAXWIDTH),
.B_WIDTH(`DSP_B_MAXWIDTH),
.Y_WIDTH(`MIN(Y_WIDTH,`DSP_A_MAXWIDTH+`DSP_B_MAXWIDTH)),
) _TECHMAP_REPLACE_ (
.A(Aext),
.B(Bext),
.Y(Y)
);
end
end
endgenerate
endmodule
(* techmap_celltype = "$mul $__mul" *)
module _90_soft_mul (A, B, Y);
parameter A_SIGNED = 0;
parameter B_SIGNED = 0;
parameter A_WIDTH = 1;
parameter B_WIDTH = 1;
parameter Y_WIDTH = 1;
input [A_WIDTH-1:0] A;
input [B_WIDTH-1:0] B;
output [Y_WIDTH-1:0] Y;
// Indirection necessary since mapping
// back to $mul will cause recursion
generate
if (A_SIGNED && !B_SIGNED)
\$__soft_mul #(
.A_SIGNED(A_SIGNED),
.B_SIGNED(1),
.A_WIDTH(A_WIDTH),
.B_WIDTH(B_WIDTH+1),
.Y_WIDTH(Y_WIDTH)
) _TECHMAP_REPLACE_ (
.A(A),
.B({1'b0,B}),
.Y(Y)
);
else if (!A_SIGNED && B_SIGNED)
\$__soft_mul #(
.A_SIGNED(1),
.B_SIGNED(B_SIGNED),
.A_WIDTH(A_WIDTH+1),
.B_WIDTH(B_WIDTH),
.Y_WIDTH(Y_WIDTH)
) _TECHMAP_REPLACE_ (
.A({1'b0,A}),
.B(B),
.Y(Y)
);
else
\$__soft_mul #(
.A_SIGNED(A_SIGNED),
.B_SIGNED(B_SIGNED),
.A_WIDTH(A_WIDTH),
.B_WIDTH(B_WIDTH),
.Y_WIDTH(Y_WIDTH)
) _TECHMAP_REPLACE_ (
.A(A),
.B(B),
.Y(Y)
);
endgenerate
endmodule

View File

@ -6,4 +6,5 @@ bram_conn_2.vh
bram_conn_4.vh
bram_conn_9.vh
bram_conn_18.vh
bram_conn_36.vh
brams_connect.mk

View File

@ -13,25 +13,26 @@ $(eval $(call add_share_file,share/ecp5,techlibs/ecp5/brams_map.v))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/bram.txt))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/arith_map.v))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/latches_map.v))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/dsp_map.v))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc_map.v))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc_unmap.v))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc_model.v))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc_5g.box))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc_5g.lut))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc_5g_nowide.lut))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc9_map.v))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc9_unmap.v))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc9_model.v))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc9_5g.box))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc9_5g.lut))
$(eval $(call add_share_file,share/ecp5,techlibs/ecp5/abc9_5g_nowide.lut))
EXTRA_OBJS += techlibs/ecp5/brams_init.mk techlibs/ecp5/brams_connect.mk
.SECONDARY: techlibs/ecp5/brams_init.mk techlibs/ecp5/brams_connect.mk
techlibs/ecp5/brams_init.mk: techlibs/ecp5/brams_init.py
$(Q) mkdir -p techlibs/ecp5
$(P) python3 $<
$(P) $(PYTHON_EXECUTABLE) $<
$(Q) touch $@
techlibs/ecp5/brams_connect.mk: techlibs/ecp5/brams_connect.py
$(Q) mkdir -p techlibs/ecp5
$(P) python3 $<
$(P) $(PYTHON_EXECUTABLE) $<
$(Q) touch $@
@ -43,6 +44,7 @@ techlibs/ecp5/bram_conn_2.vh: techlibs/ecp5/brams_connect.mk
techlibs/ecp5/bram_conn_4.vh: techlibs/ecp5/brams_connect.mk
techlibs/ecp5/bram_conn_9.vh: techlibs/ecp5/brams_connect.mk
techlibs/ecp5/bram_conn_18.vh: techlibs/ecp5/brams_connect.mk
techlibs/ecp5/bram_conn_36.vh: techlibs/ecp5/brams_connect.mk
$(eval $(call add_gen_share_file,share/ecp5,techlibs/ecp5/bram_init_1_2_4.vh))
$(eval $(call add_gen_share_file,share/ecp5,techlibs/ecp5/bram_init_9_18_36.vh))
@ -52,3 +54,4 @@ $(eval $(call add_gen_share_file,share/ecp5,techlibs/ecp5/bram_conn_2.vh))
$(eval $(call add_gen_share_file,share/ecp5,techlibs/ecp5/bram_conn_4.vh))
$(eval $(call add_gen_share_file,share/ecp5,techlibs/ecp5/bram_conn_9.vh))
$(eval $(call add_gen_share_file,share/ecp5,techlibs/ecp5/bram_conn_18.vh))
$(eval $(call add_gen_share_file,share/ecp5,techlibs/ecp5/bram_conn_36.vh))

View File

@ -18,7 +18,7 @@ CCU2C 1 1 9 3
# Box 2 : TRELLIS_DPR16X4_COMB (16x4 dist ram)
# Outputs: DO0, DO1, DO2, DO3
# name ID w/b ins outs
$__ABC_DPR16X4_COMB 2 0 8 4
$__ABC9_DPR16X4_COMB 2 0 8 4
#A0 A1 A2 A3 RAD0 RAD1 RAD2 RAD3
0 0 0 0 141 379 275 379

View File

@ -20,5 +20,5 @@ module TRELLIS_DPR16X4 (
.RAD(RAD), .DO(\$DO )
);
\$__ABC_DPR16X4_COMB do (.A(\$DO ), .S(RAD), .Y(DO));
\$__ABC9_DPR16X4_COMB do (.A(\$DO ), .S(RAD), .Y(DO));
endmodule

View File

@ -0,0 +1,5 @@
// ---------------------------------------
(* abc9_box_id=2 *)
module \$__ABC9_DPR16X4_COMB (input [3:0] A, S, output [3:0] Y);
endmodule

View File

@ -1,5 +1,5 @@
// ---------------------------------------
module \$__ABC_DPR16X4_COMB (input [3:0] A, S, output [3:0] Y);
module \$__ABC9_DPR16X4_COMB (input [3:0] A, S, output [3:0] Y);
assign Y = A;
endmodule

View File

@ -1,5 +0,0 @@
// ---------------------------------------
(* abc_box_id=2 *)
module \$__ABC_DPR16X4_COMB (input [3:0] A, S, output [3:0] Y);
endmodule

View File

@ -1,3 +1,18 @@
bram $__ECP5_PDPW16KD
init 1
abits 9
dbits 36
groups 2
ports 1 1
wrmode 1 0
enable 4 1
transp 0 0
clocks 2 3
clkpol 2 3
endbram
bram $__ECP5_DP16KD
init 1
@ -22,8 +37,16 @@ bram $__ECP5_DP16KD
clkpol 2 3
endbram
match $__ECP5_PDPW16KD
min bits 2048
min efficiency 5
shuffle_enable A
make_transp
or_next_if_better
endmatch
match $__ECP5_DP16KD
min bits 2048
min efficiency 5
shuffle_enable B
shuffle_enable A
endmatch

View File

@ -10,6 +10,18 @@ def write_bus_ports(f, ada_bits, adb_bits, dia_bits, dob_bits):
print(" %s," % ", ".join(dia_conn), file=f)
print(" %s," % ", ".join(dob_conn), file=f)
def write_bus_ports_pdp(f, adw_bits, adr_bits, di_bits, do_bits, be_bits):
adw_conn = [".ADW%d(%s)" % (i, adw_bits[i]) for i in range(len(adw_bits))]
adr_conn = [".ADR%d(%s)" % (i, adr_bits[i]) for i in range(len(adr_bits))]
di_conn = [".DI%d(%s)" % (i, di_bits[i]) for i in range(len(di_bits))]
do_conn = [".DO%d(%s)" % (i, do_bits[i]) for i in range(len(do_bits))]
be_conn = [".BE%d(%s)" % (i, be_bits[i]) for i in range(len(be_bits))]
print(" %s," % ", ".join(adw_conn), file=f)
print(" %s," % ", ".join(adr_conn), file=f)
print(" %s," % ", ".join(di_conn), file=f)
print(" %s," % ", ".join(do_conn), file=f)
print(" %s," % ", ".join(be_conn), file=f)
with open("techlibs/ecp5/bram_conn_1.vh", "w") as f:
ada_bits = ["A1ADDR[%d]" % i for i in range(14)]
adb_bits = ["B1ADDR[%d]" % i for i in range(14)]
@ -44,3 +56,11 @@ with open("techlibs/ecp5/bram_conn_18.vh", "w") as f:
dia_bits = ["A1DATA[%d]" % i for i in range(18)]
dob_bits = ["B1DATA[%d]" % i for i in range(18)]
write_bus_ports(f, ada_bits, adb_bits, dia_bits, dob_bits)
with open("techlibs/ecp5/bram_conn_36.vh", "w") as f:
adw_bits = ["A1ADDR[%d]" % i for i in range(9)]
adr_bits = ["1'b0", "1'b0", "1'b0", "1'b0", "1'b0"] + ["B1ADDR[%d]" % i for i in range(9)]
di_bits = ["A1DATA[%d]" % i for i in range(36)]
do_bits = ["B1DATA[%d]" % (i + 18) for i in range(18)] + ["B1DATA[%d]" % i for i in range(18)]
be_bits = ["A1EN[%d]" % i for i in range(4)]
write_bus_ports_pdp(f, adw_bits, adr_bits, di_bits, do_bits, be_bits)

View File

@ -113,3 +113,45 @@ module \$__ECP5_DP16KD (CLK2, CLK3, A1ADDR, A1DATA, A1EN, B1ADDR, B1DATA, B1EN);
wire TECHMAP_FAIL = 1'b1;
end endgenerate
endmodule
module \$__ECP5_PDPW16KD (CLK2, CLK3, A1ADDR, A1DATA, A1EN, B1ADDR, B1DATA, B1EN);
parameter CFG_ABITS = 9;
parameter CFG_DBITS = 36;
parameter CFG_ENABLE_A = 4;
parameter CLKPOL2 = 1;
parameter CLKPOL3 = 1;
parameter [18431:0] INIT = 18432'bx;
input CLK2;
input CLK3;
input [CFG_ABITS-1:0] A1ADDR;
input [CFG_DBITS-1:0] A1DATA;
input [CFG_ENABLE_A-1:0] A1EN;
input [CFG_ABITS-1:0] B1ADDR;
output [CFG_DBITS-1:0] B1DATA;
input B1EN;
localparam CLKWMUX = CLKPOL2 ? "CLKA" : "INV";
localparam CLKRMUX = CLKPOL3 ? "CLKB" : "INV";
localparam WRITEMODE_A = TRANSP2 ? "WRITETHROUGH" : "READBEFOREWRITE";
PDPW16KD #(
`include "bram_init_9_18_36.vh"
.DATA_WIDTH_W(36),
.DATA_WIDTH_R(36),
.CLKWMUX(CLKWMUX),
.CLKRMUX(CLKRMUX),
.GSR("AUTO")
) _TECHMAP_REPLACE_ (
`include "bram_conn_36.vh"
.CLKW(CLK2), .CLKR(CLK3),
.CEW(1'b1),
.CER(B1EN), .OCER(1'b1),
.RST(1'b0)
);
endmodule

View File

@ -333,6 +333,13 @@ module ECLKSYNCB(
);
endmodule
(* blackbox *)
module ECLKBRIDGECS(
input CLK0, CLK1, SEL,
output ECSOUT
);
endmodule
(* blackbox *)
module DCCA(
input CLKI, CE,
@ -684,3 +691,97 @@ module SGSR (
input GSR, CLK
);
endmodule
(* blackbox *)
module PDPW16KD (
input DI35, DI34, DI33, DI32, DI31, DI30, DI29, DI28, DI27, DI26, DI25, DI24, DI23, DI22, DI21, DI20, DI19, DI18,
input DI17, DI16, DI15, DI14, DI13, DI12, DI11, DI10, DI9, DI8, DI7, DI6, DI5, DI4, DI3, DI2, DI1, DI0,
input ADW8, ADW7, ADW6, ADW5, ADW4, ADW3, ADW2, ADW1, ADW0,
input BE3, BE2, BE1, BE0, CEW, CLKW, CSW2, CSW1, CSW0,
input ADR13, ADR12, ADR11, ADR10, ADR9, ADR8, ADR7, ADR6, ADR5, ADR4, ADR3, ADR2, ADR1, ADR0,
input CER, OCER, CLKR, CSR2, CSR1, CSR0, RST,
output DO35, DO34, DO33, DO32, DO31, DO30, DO29, DO28, DO27, DO26, DO25, DO24, DO23, DO22, DO21, DO20, DO19, DO18,
output DO17, DO16, DO15, DO14, DO13, DO12, DO11, DO10, DO9, DO8, DO7, DO6, DO5, DO4, DO3, DO2, DO1, DO0
);
parameter DATA_WIDTH_W = 36;
parameter DATA_WIDTH_R = 36;
parameter GSR = "ENABLED";
parameter REGMODE = "NOREG";
parameter RESETMODE = "SYNC";
parameter ASYNC_RESET_RELEASE = "SYNC";
parameter CSDECODE_W = "0b000";
parameter CSDECODE_R = "0b000";
parameter INITVAL_00 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_01 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_02 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_03 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_04 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_05 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_06 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_07 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_08 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_09 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_0A = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_0B = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_0C = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_0D = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_0E = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_0F = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_10 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_11 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_12 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_13 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_14 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_15 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_16 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_17 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_18 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_19 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_1A = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_1B = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_1C = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_1D = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_1E = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_1F = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_20 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_21 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_22 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_23 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_24 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_25 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_26 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_27 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_28 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_29 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_2A = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_2B = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_2C = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_2D = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_2E = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_2F = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_30 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_31 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_32 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_33 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_34 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_35 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_36 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_37 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_38 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_39 = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_3A = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_3B = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_3C = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_3D = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_3E = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INITVAL_3F = 320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
parameter INIT_DATA = "STATIC";
parameter CLKWMUX = "CLKW";
parameter CLKRMUX = "CLKR";
endmodule

View File

@ -23,15 +23,15 @@ module FD1S3JX(input PD, D, CK, output Q); parameter GSR = "ENABLED"; TRELLI
// module FL1S3AY(); endmodule
// Diamond I/O registers
module IFS1P3BX(input PD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("SET"), .SRMODE("ASYNC")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(PD), .CE(SP), .DI(D), .Q(Q)); endmodule
module IFS1P3DX(input CD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("RESET"), .SRMODE("ASYNC")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(CD), .CE(SP), .DI(D), .Q(Q)); endmodule
module IFS1P3IX(input CD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("RESET"), .SRMODE("LSR_OVER_CE")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(CD), .CE(SP), .DI(D), .Q(Q)); endmodule
module IFS1P3JX(input PD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("SET"), .SRMODE("LSR_OVER_CE")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(PD), .CE(SP), .DI(D), .Q(Q)); endmodule
module IFS1P3BX(input PD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; (* syn_useioff, ioff_dir="input" *) TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("SET"), .SRMODE("ASYNC")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(PD), .CE(SP), .DI(D), .Q(Q)); endmodule
module IFS1P3DX(input CD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; (* syn_useioff, ioff_dir="input" *) TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("RESET"), .SRMODE("ASYNC")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(CD), .CE(SP), .DI(D), .Q(Q)); endmodule
module IFS1P3IX(input CD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; (* syn_useioff, ioff_dir="input" *) TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("RESET"), .SRMODE("LSR_OVER_CE")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(CD), .CE(SP), .DI(D), .Q(Q)); endmodule
module IFS1P3JX(input PD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; (* syn_useioff, ioff_dir="input" *) TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("SET"), .SRMODE("LSR_OVER_CE")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(PD), .CE(SP), .DI(D), .Q(Q)); endmodule
module OFS1P3BX(input PD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("SET"), .SRMODE("ASYNC")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(PD), .CE(SP), .DI(D), .Q(Q)); endmodule
module OFS1P3DX(input CD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("RESET"), .SRMODE("ASYNC")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(CD), .CE(SP), .DI(D), .Q(Q)); endmodule
module OFS1P3IX(input CD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("RESET"), .SRMODE("LSR_OVER_CE")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(CD), .CE(SP), .DI(D), .Q(Q)); endmodule
module OFS1P3JX(input PD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("SET"), .SRMODE("LSR_OVER_CE")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(PD), .CE(SP), .DI(D), .Q(Q)); endmodule
module OFS1P3BX(input PD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; (* syn_useioff, ioff_dir="output" *) TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("SET"), .SRMODE("ASYNC")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(PD), .CE(SP), .DI(D), .Q(Q)); endmodule
module OFS1P3DX(input CD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; (* syn_useioff, ioff_dir="output" *) TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("RESET"), .SRMODE("ASYNC")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(CD), .CE(SP), .DI(D), .Q(Q)); endmodule
module OFS1P3IX(input CD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; (* syn_useioff, ioff_dir="output" *) TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("RESET"), .SRMODE("LSR_OVER_CE")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(CD), .CE(SP), .DI(D), .Q(Q)); endmodule
module OFS1P3JX(input PD, D, SP, SCLK, output Q); parameter GSR = "ENABLED"; (* syn_useioff, ioff_dir="output" *) TRELLIS_FF #(.GSR(GSR), .CEMUX("CE"), .CLKMUX("CLK"), .LSRMUX("LSR"), .REGSET("SET"), .SRMODE("LSR_OVER_CE")) _TECHMAP_REPLACE_ (.CLK(SCLK), .LSR(PD), .CE(SP), .DI(D), .Q(Q)); endmodule
// TODO: Diamond I/O latches
// module IFS1S1B(input PD, D, SCLK, output Q); endmodule

View File

@ -9,19 +9,19 @@ module LUT4(input A, B, C, D, output Z);
endmodule
// ---------------------------------------
(* abc_box_id=4, lib_whitebox *)
(* abc9_box_id=4, lib_whitebox *)
module L6MUX21 (input D0, D1, SD, output Z);
assign Z = SD ? D1 : D0;
endmodule
// ---------------------------------------
(* abc_box_id=1, lib_whitebox *)
(* abc9_box_id=1, lib_whitebox *)
module CCU2C(
(* abc_carry *)
(* abc9_carry *)
input CIN,
input A0, B0, C0, D0, A1, B1, C1, D1,
output S0, S1,
(* abc_carry *)
(* abc9_carry *)
output COUT
);
parameter [15:0] INIT0 = 16'h0000;
@ -103,7 +103,7 @@ module TRELLIS_RAM16X2 (
endmodule
// ---------------------------------------
(* abc_box_id=3, lib_whitebox *)
(* abc9_box_id=3, lib_whitebox *)
module PFUMX (input ALUT, BLUT, C0, output Z);
assign Z = C0 ? ALUT : BLUT;
endmodule
@ -115,7 +115,7 @@ module TRELLIS_DPR16X4 (
input WRE,
input WCK,
input [3:0] RAD,
/* (* abc_arrival=<TODO> *) */
/* (* abc9_arrival=<TODO> *) */
output [3:0] DO
);
parameter WCKMUX = "WCK";

17
techlibs/ecp5/dsp_map.v Normal file
View File

@ -0,0 +1,17 @@
module \$__MUL18X18 (input [17:0] A, input [17:0] B, output [35:0] Y);
parameter A_WIDTH = 18;
parameter B_WIDTH = 18;
parameter Y_WIDTH = 36;
parameter A_SIGNED = 0;
parameter B_SIGNED = 0;
MULT18X18D _TECHMAP_REPLACE_ (
.A0(A[0]), .A1(A[1]), .A2(A[2]), .A3(A[3]), .A4(A[4]), .A5(A[5]), .A6(A[6]), .A7(A[7]), .A8(A[8]), .A9(A[9]), .A10(A[10]), .A11(A[11]), .A12(A[12]), .A13(A[13]), .A14(A[14]), .A15(A[15]), .A16(A[16]), .A17(A[17]),
.B0(B[0]), .B1(B[1]), .B2(B[2]), .B3(B[3]), .B4(B[4]), .B5(B[5]), .B6(B[6]), .B7(B[7]), .B8(B[8]), .B9(B[9]), .B10(B[10]), .B11(B[11]), .B12(B[12]), .B13(B[13]), .B14(B[14]), .B15(B[15]), .B16(B[16]), .B17(B[17]),
.C17(1'b0), .C16(1'b0), .C15(1'b0), .C14(1'b0), .C13(1'b0), .C12(1'b0), .C11(1'b0), .C10(1'b0), .C9(1'b0), .C8(1'b0), .C7(1'b0), .C6(1'b0), .C5(1'b0), .C4(1'b0), .C3(1'b0), .C2(1'b0), .C1(1'b0), .C0(1'b0),
.SIGNEDA(A_SIGNED), .SIGNEDB(B_SIGNED), .SOURCEA(1'b0), .SOURCEB(1'b0),
.P0(Y[0]), .P1(Y[1]), .P2(Y[2]), .P3(Y[3]), .P4(Y[4]), .P5(Y[5]), .P6(Y[6]), .P7(Y[7]), .P8(Y[8]), .P9(Y[9]), .P10(Y[10]), .P11(Y[11]), .P12(Y[12]), .P13(Y[13]), .P14(Y[14]), .P15(Y[15]), .P16(Y[16]), .P17(Y[17]), .P18(Y[18]), .P19(Y[19]), .P20(Y[20]), .P21(Y[21]), .P22(Y[22]), .P23(Y[23]), .P24(Y[24]), .P25(Y[25]), .P26(Y[26]), .P27(Y[27]), .P28(Y[28]), .P29(Y[29]), .P30(Y[30]), .P31(Y[31]), .P32(Y[32]), .P33(Y[33]), .P34(Y[34]), .P35(Y[35])
);
endmodule

View File

@ -89,6 +89,9 @@ struct SynthEcp5Pass : public ScriptPass
log(" generate an output netlist (and BLIF file) suitable for VPR\n");
log(" (this feature is experimental and incomplete)\n");
log("\n");
log(" -nodsp\n");
log(" do not map multipliers to MULT18X18D\n");
log("\n");
log("\n");
log("The following commands are executed by this synthesis command:\n");
help_script();
@ -96,7 +99,7 @@ struct SynthEcp5Pass : public ScriptPass
}
string top_opt, blif_file, edif_file, json_file;
bool noccu2, nodffe, nobram, nolutram, nowidelut, flatten, retime, abc2, abc9, vpr;
bool noccu2, nodffe, nobram, nolutram, nowidelut, flatten, retime, abc2, abc9, nodsp, vpr;
void clear_flags() YS_OVERRIDE
{
@ -114,6 +117,7 @@ struct SynthEcp5Pass : public ScriptPass
abc2 = false;
vpr = false;
abc9 = false;
nodsp = false;
}
void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
@ -192,6 +196,10 @@ struct SynthEcp5Pass : public ScriptPass
abc9 = true;
continue;
}
if (args[argidx] == "-nodsp") {
nodsp = true;
continue;
}
break;
}
extra_args(args, argidx, design);
@ -218,17 +226,34 @@ struct SynthEcp5Pass : public ScriptPass
run(stringf("hierarchy -check %s", help_mode ? "-top <top>" : top_opt.c_str()));
}
if (flatten && check_label("flatten", "(unless -noflatten)"))
if (check_label("coarse"))
{
run("proc");
if (flatten || help_mode)
run("flatten");
run("tribuf -logic");
run("deminout");
run("opt_expr");
run("opt_clean");
run("check");
run("opt");
run("wreduce");
run("peepopt");
run("opt_clean");
run("share");
run("techmap -map +/cmp2lut.v -D LUT_WIDTH=4");
run("opt_expr");
run("opt_clean");
if (!nodsp) {
run("techmap -map +/mul2dsp.v -map +/ecp5/dsp_map.v -D DSP_A_MAXWIDTH=18 -D DSP_B_MAXWIDTH=18 -D DSP_A_MINWIDTH=2 -D DSP_B_MINWIDTH=2 -D DSP_NAME=$__MUL18X18", "(unless -nodsp)");
run("chtype -set $mul t:$__soft_mul", "(unless -nodsp)");
}
if (check_label("coarse"))
{
run("synth -run coarse");
run("alumacc");
run("opt");
run("fsm");
run("opt -fast");
run("memory -nomap");
run("opt_clean");
}
if (!nobram && check_label("map_bram", "(skip if -nobram)"))
@ -272,6 +297,7 @@ struct SynthEcp5Pass : public ScriptPass
run("simplemap");
run("ecp5_ffinit");
run("ecp5_gsr");
run("attrmvcp -copy -attr syn_useioff");
run("opt_clean");
}
@ -282,15 +308,16 @@ struct SynthEcp5Pass : public ScriptPass
}
std::string techmap_args = "-map +/ecp5/latches_map.v";
if (abc9)
techmap_args += " -map +/ecp5/abc_map.v -max_iter 1";
techmap_args += " -map +/ecp5/abc9_map.v -max_iter 1";
run("techmap " + techmap_args);
if (abc9) {
run("read_verilog -icells -lib +/ecp5/abc9_model.v");
if (nowidelut)
run("abc9 -lut +/ecp5/abc_5g_nowide.lut -box +/ecp5/abc_5g.box -W 200");
run("abc9 -lut +/ecp5/abc9_5g_nowide.lut -box +/ecp5/abc9_5g.box -W 200 -nomfs");
else
run("abc9 -lut +/ecp5/abc_5g.lut -box +/ecp5/abc_5g.box -W 200");
run("techmap -map +/ecp5/abc_unmap.v");
run("abc9 -lut +/ecp5/abc9_5g.lut -box +/ecp5/abc9_5g.box -W 200 -nomfs");
run("techmap -map +/ecp5/abc9_unmap.v");
} else {
if (nowidelut)
run("abc -lut 4 -dress");
@ -312,6 +339,7 @@ struct SynthEcp5Pass : public ScriptPass
if (check_label("check"))
{
run("autoname");
run("hierarchy -check");
run("stat");
run("check -noinit");

View File

@ -17,6 +17,18 @@ module \$_DFF_NP1_ (input D, C, R, output Q); EFX_FF #(.CLK_POLARITY(1'b0), .CE
module \$_DFF_PP0_ (input D, C, R, output Q); EFX_FF #(.CLK_POLARITY(1'b1), .CE_POLARITY(1'b1), .SR_POLARITY(1'b1), .D_POLARITY(1'b1), .SR_SYNC(1'b0), .SR_VALUE(1'b0), .SR_SYNC_PRIORITY(1'b1)) _TECHMAP_REPLACE_ (.D(D), .CE(1'b1), .CLK(C), .SR(R), .Q(Q)); endmodule
module \$_DFF_PP1_ (input D, C, R, output Q); EFX_FF #(.CLK_POLARITY(1'b1), .CE_POLARITY(1'b1), .SR_POLARITY(1'b1), .D_POLARITY(1'b1), .SR_SYNC(1'b0), .SR_VALUE(1'b1), .SR_SYNC_PRIORITY(1'b1)) _TECHMAP_REPLACE_ (.D(D), .CE(1'b1), .CLK(C), .SR(R), .Q(Q)); endmodule
module \$_DLATCH_N_ (E, D, Q);
wire [1023:0] _TECHMAP_DO_ = "simplemap; opt";
input E, D;
output Q = !E ? D : Q;
endmodule
module \$_DLATCH_P_ (E, D, Q);
wire [1023:0] _TECHMAP_DO_ = "simplemap; opt";
input E, D;
output Q = E ? D : Q;
endmodule
`ifndef NO_LUT
module \$lut (A, Y);
parameter WIDTH = 0;

View File

@ -60,6 +60,8 @@ module EFX_FF(
assign sr = SR_POLARITY ? SR : ~SR;
assign d = D_POLARITY ? D : ~D;
initial Q = 1'b0;
generate
if (SR_SYNC == 1)
begin

2
techlibs/gowin/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
brams_init.mk
bram_init_*.vh

View File

@ -15,3 +15,13 @@ $(eval $(call add_share_file,share/gowin,techlibs/gowin/dram.txt))
$(eval $(call add_share_file,share/gowin,techlibs/gowin/brams_init3.vh))
EXTRA_OBJS += techlibs/gowin/brams_init.mk
.SECONDARY: techlibs/gowin/brams_init.mk
techlibs/gowin/brams_init.mk: techlibs/gowin/brams_init.py
$(Q) mkdir -p techlibs/gowin
$(P) python3 $<
$(Q) touch $@
techlibs/gowin/bram_init_16.vh: techlibs/gowin/brams_init.mk
$(eval $(call add_gen_share_file,share/gowin,techlibs/gowin/bram_init_16.vh))

View File

@ -40,15 +40,15 @@ module _80_gw1n_alu(A, B, CI, BI, X, Y, CO);
\$pos #(.A_SIGNED(B_SIGNED), .A_WIDTH(B_WIDTH), .Y_WIDTH(Y_WIDTH)) B_conv (.A(B), .Y(B_buf));
wire [Y_WIDTH-1:0] AA = A_buf;
wire [Y_WIDTH-1:0] BB = BI ? ~B_buf : B_buf;
wire [Y_WIDTH-1:0] BB = B_buf;
wire [Y_WIDTH-1:0] C = {CO, CI};
genvar i;
generate for (i = 0; i < Y_WIDTH; i = i + 1) begin:slice
ALU #(.ALU_MODE(32'b0))
ALU #(.ALU_MODE(2)) // ADDSUB I3 ? add : sub
alu(.I0(AA[i]),
.I1(BB[i]),
.I3(1'b0),
.I3(~BI),
.CIN(C[i]),
.COUT(CO[i]),
.SUM(Y[i])

View File

@ -1,6 +1,7 @@
bram $__GW1NR_SDP
# uncomment when done
# init 1
init 1
abits 9 @a9d36
dbits 32 @a9d36
abits 10 @a10d18
dbits 16 @a10d18
abits 11 @a11d9
@ -14,7 +15,8 @@ bram $__GW1NR_SDP
groups 2
ports 1 1
wrmode 1 0
enable 1 1 @a10d18
enable 4 1 @a9d36
enable 2 1 @a10d18
enable 1 1 @a11d9 @a12d4 @a13d2 @a14d1
transp 0 0
clocks 2 3
@ -24,6 +26,6 @@ endbram
match $__GW1NR_SDP
min bits 2048
min efficiency 5
shuffle_enable B
shuffle_enable A
make_transp
endmatch

8
techlibs/gowin/brams_init.py Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env python3
with open("techlibs/gowin/bram_init_16.vh", "w") as f:
for i in range(0, 0x40):
low = i << 8
hi = ((i+1) << 8)-1
snippet = "INIT[%d:%d]" % (hi, low)
print(".INIT_RAM_%02X({%s})," % (i, snippet), file=f)

Some files were not shown because too many files have changed in this diff Show More