Merge pull request #358 from YosysHQ/generic-improve

Generic Arch improvements
diff --git a/.cirrus.yml b/.cirrus.yml
index 5cac994..bdb3c48 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -11,6 +11,6 @@
   test_ice40_script: cd build && ./nextpnr-ice40-test
   smoketest_ice40_script: export NEXTPNR=$(pwd)/build/nextpnr-ice40 && cd ice40/smoketest/attosoc && ./smoketest.sh
   test_ecp5_script: cd build && ./nextpnr-ecp5-test
-  smoketest_generic_script: export NEXTPNR=$(pwd)/build/nextpnr-generic && cd generic/examples && ./simple.sh
+  smoketest_generic_script: export NEXTPNR=$(pwd)/build/nextpnr-generic && cd generic/examples && ./simple.sh && ./simtest.sh
   regressiontest_ice40_script: make -j $(nproc) -C tests/ice40/regressions NPNR=$(pwd)/build/nextpnr-ice40
   regressiontest_ecp5_script: make -j $(nproc) -C tests/ecp5/regressions NPNR=$(pwd)/build/nextpnr-ecp5
diff --git a/generic/arch.cc b/generic/arch.cc
index 14d1511..c1c01d2 100644
--- a/generic/arch.cc
+++ b/generic/arch.cc
@@ -27,6 +27,30 @@
 
 NEXTPNR_NAMESPACE_BEGIN
 
+WireInfo &Arch::wire_info(IdString wire)
+{
+    auto w = wires.find(wire);
+    if (w == wires.end())
+        NPNR_ASSERT_FALSE_STR("no wire named " + wire.str(this));
+    return w->second;
+}
+
+PipInfo &Arch::pip_info(IdString pip)
+{
+    auto p = pips.find(pip);
+    if (p == pips.end())
+        NPNR_ASSERT_FALSE_STR("no pip named " + pip.str(this));
+    return p->second;
+}
+
+BelInfo &Arch::bel_info(IdString bel)
+{
+    auto b = bels.find(bel);
+    if (b == bels.end())
+        NPNR_ASSERT_FALSE_STR("no bel named " + bel.str(this));
+    return b->second;
+}
+
 void Arch::addWire(IdString name, IdString type, int x, int y)
 {
     NPNR_ASSERT(wires.count(name) == 0);
@@ -50,8 +74,8 @@
     pi.delay = delay;
     pi.loc = loc;
 
-    wires.at(srcWire).downhill.push_back(name);
-    wires.at(dstWire).uphill.push_back(name);
+    wire_info(srcWire).downhill.push_back(name);
+    wire_info(dstWire).uphill.push_back(name);
     pip_ids.push_back(name);
 
     if (int(tilePipDimZ.size()) <= loc.x)
@@ -75,7 +99,7 @@
     pi.dstWire = dstWire;
     pi.delay = delay;
 
-    wires.at(srcWire).aliases.push_back(name);
+    wire_info(srcWire).aliases.push_back(name);
     pip_ids.push_back(name);
 }
 
@@ -115,38 +139,38 @@
 
 void Arch::addBelInput(IdString bel, IdString name, IdString wire)
 {
-    NPNR_ASSERT(bels.at(bel).pins.count(name) == 0);
-    PinInfo &pi = bels.at(bel).pins[name];
+    NPNR_ASSERT(bel_info(bel).pins.count(name) == 0);
+    PinInfo &pi = bel_info(bel).pins[name];
     pi.name = name;
     pi.wire = wire;
     pi.type = PORT_IN;
 
-    wires.at(wire).downhill_bel_pins.push_back(BelPin{bel, name});
-    wires.at(wire).bel_pins.push_back(BelPin{bel, name});
+    wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name});
+    wire_info(wire).bel_pins.push_back(BelPin{bel, name});
 }
 
 void Arch::addBelOutput(IdString bel, IdString name, IdString wire)
 {
-    NPNR_ASSERT(bels.at(bel).pins.count(name) == 0);
-    PinInfo &pi = bels.at(bel).pins[name];
+    NPNR_ASSERT(bel_info(bel).pins.count(name) == 0);
+    PinInfo &pi = bel_info(bel).pins[name];
     pi.name = name;
     pi.wire = wire;
     pi.type = PORT_OUT;
 
-    wires.at(wire).uphill_bel_pin = BelPin{bel, name};
-    wires.at(wire).bel_pins.push_back(BelPin{bel, name});
+    wire_info(wire).uphill_bel_pin = BelPin{bel, name};
+    wire_info(wire).bel_pins.push_back(BelPin{bel, name});
 }
 
 void Arch::addBelInout(IdString bel, IdString name, IdString wire)
 {
-    NPNR_ASSERT(bels.at(bel).pins.count(name) == 0);
-    PinInfo &pi = bels.at(bel).pins[name];
+    NPNR_ASSERT(bel_info(bel).pins.count(name) == 0);
+    PinInfo &pi = bel_info(bel).pins[name];
     pi.name = name;
     pi.wire = wire;
     pi.type = PORT_INOUT;
 
-    wires.at(wire).downhill_bel_pins.push_back(BelPin{bel, name});
-    wires.at(wire).bel_pins.push_back(BelPin{bel, name});
+    wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name});
+    wire_info(wire).bel_pins.push_back(BelPin{bel, name});
 }
 
 void Arch::addGroupBel(IdString group, IdString bel) { groups[group].bels.push_back(bel); }
@@ -165,19 +189,19 @@
 
 void Arch::setWireDecal(WireId wire, DecalXY decalxy)
 {
-    wires.at(wire).decalxy = decalxy;
+    wire_info(wire).decalxy = decalxy;
     refreshUiWire(wire);
 }
 
 void Arch::setPipDecal(PipId pip, DecalXY decalxy)
 {
-    pips.at(pip).decalxy = decalxy;
+    pip_info(pip).decalxy = decalxy;
     refreshUiPip(pip);
 }
 
 void Arch::setBelDecal(BelId bel, DecalXY decalxy)
 {
-    bels.at(bel).decalxy = decalxy;
+    bel_info(bel).decalxy = decalxy;
     refreshUiBel(bel);
 }
 
@@ -187,11 +211,11 @@
     refreshUiGroup(group);
 }
 
-void Arch::setWireAttr(IdString wire, IdString key, const std::string &value) { wires.at(wire).attrs[key] = value; }
+void Arch::setWireAttr(IdString wire, IdString key, const std::string &value) { wire_info(wire).attrs[key] = value; }
 
-void Arch::setPipAttr(IdString pip, IdString key, const std::string &value) { pips.at(pip).attrs[key] = value; }
+void Arch::setPipAttr(IdString pip, IdString key, const std::string &value) { pip_info(pip).attrs[key] = value; }
 
-void Arch::setBelAttr(IdString bel, IdString key, const std::string &value) { bels.at(bel).attrs[key] = value; }
+void Arch::setBelAttr(IdString bel, IdString key, const std::string &value) { bel_info(bel).attrs[key] = value; }
 
 void Arch::setLutK(int K) { args.K = K; }
 
diff --git a/generic/arch.h b/generic/arch.h
index e9d3593..444d263 100644
--- a/generic/arch.h
+++ b/generic/arch.h
@@ -122,6 +122,11 @@
     std::unordered_map<IdString, BelInfo> bels;
     std::unordered_map<GroupId, GroupInfo> groups;
 
+    // These functions include useful errors if not found
+    WireInfo &wire_info(IdString wire);
+    PipInfo &pip_info(IdString wire);
+    BelInfo &bel_info(IdString wire);
+
     std::vector<IdString> bel_ids, wire_ids, pip_ids;
 
     std::unordered_map<Loc, BelId> bel_by_loc;
diff --git a/generic/examples/.gitignore b/generic/examples/.gitignore
index 38e95de..ad2fba2 100644
--- a/generic/examples/.gitignore
+++ b/generic/examples/.gitignore
@@ -1,3 +1,6 @@
 blinky.fasm
 __pycache__
 *.pyc
+pnrblinky.v
+/blinky_simtest
+*.vcd
diff --git a/generic/examples/blinky.v b/generic/examples/blinky.v
index b7cb1b8..42becb7 100644
--- a/generic/examples/blinky.v
+++ b/generic/examples/blinky.v
@@ -1,9 +1,12 @@
-module top(input clk, output reg [7:0] leds);
+module top(input clk, rst, output reg [7:0] leds);
 
-reg [25:0] ctr;
+reg [7:0] ctr;
 always @(posedge clk)
-	ctr <= ctr + 1'b1;
+	if (rst)
+		ctr <= 8'h00;
+	else
+		ctr <= ctr + 1'b1;
 
-assign leds = ctr[25:18];
+assign leds = ctr;
 
-endmodule
\ No newline at end of file
+endmodule
diff --git a/generic/examples/blinky_tb.v b/generic/examples/blinky_tb.v
new file mode 100644
index 0000000..f9925e6
--- /dev/null
+++ b/generic/examples/blinky_tb.v
@@ -0,0 +1,38 @@
+`timescale 1ns / 1ps
+module blinky_tb;
+
+reg clk = 1'b0, rst = 1'b0;
+reg [7:0] ctr_gold = 8'h00;
+wire [7:0] ctr_gate;
+top dut_i(.clk(clk), .rst(rst), .leds(ctr_gate));
+
+task oneclk;
+	begin
+		clk = 1'b1;
+		#10;
+		clk = 1'b0;
+		#10;
+	end
+endtask
+
+initial begin
+	$dumpfile("blinky_simtest.vcd");
+	$dumpvars(0, blinky_tb);
+	#100;
+	rst = 1'b1;
+	repeat (5) oneclk;
+	#5
+	rst = 1'b0;
+	#5
+	repeat (500) begin
+		if (ctr_gold !== ctr_gate) begin
+			$display("mismatch gold=%b gate=%b", ctr_gold, ctr_gate);
+			$stop;
+		end
+		oneclk;
+		ctr_gold = ctr_gold + 1'b1;
+	end
+	$finish;
+end
+
+endmodule
diff --git a/generic/examples/simtest.sh b/generic/examples/simtest.sh
new file mode 100755
index 0000000..ef32891
--- /dev/null
+++ b/generic/examples/simtest.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+set -ex
+yosys -p "tcl ../synth/synth_generic.tcl 4 blinky.json" blinky.v
+${NEXTPNR:-../../nextpnr-generic} --no-iobs --pre-pack simple.py --pre-place simple_timing.py --json blinky.json --post-route bitstream.py --write pnrblinky.json
+yosys -p "read_json pnrblinky.json; write_verilog -noattr -norename pnrblinky.v"
+iverilog -o blinky_simtest ../synth/prims.v blinky_tb.v pnrblinky.v
+vvp -N ./blinky_simtest
diff --git a/generic/main.cc b/generic/main.cc
index 7dfc6aa..bb78099 100644
--- a/generic/main.cc
+++ b/generic/main.cc
@@ -46,6 +46,7 @@
 {
     po::options_description specific("Architecture specific options");
     specific.add_options()("generic", "set device type to generic");
+    specific.add_options()("no-iobs", "disable automatic IO buffer insertion");
     return specific;
 }
 
@@ -59,7 +60,10 @@
         if (arch_name != "generic")
             log_error("Unsuported architecture '%s'.\n", arch_name.c_str());
     }
-    return std::unique_ptr<Context>(new Context(chipArgs));
+    auto ctx = std::unique_ptr<Context>(new Context(chipArgs));
+    if (vm.count("no-iobs"))
+        ctx->settings[ctx->id("disable_iobs")] = Property::State::S1;
+    return ctx;
 }
 
 int main(int argc, char *argv[])
diff --git a/generic/pack.cc b/generic/pack.cc
index 69f248d..19266ab 100644
--- a/generic/pack.cc
+++ b/generic/pack.cc
@@ -249,6 +249,10 @@
                         delete_nets.insert(net2->name);
                     }
                 }
+            } else if (bool_or_default(ctx->settings, ctx->id("disable_iobs"))) {
+                // No IO buffer insertion; just remove nextpnr_[io]buf
+                for (auto &p : ci->ports)
+                    disconnect_port(ctx, ci, p.first);
             } else {
                 // Create a GENERIC_IOB buffer
                 std::unique_ptr<CellInfo> ice_cell =
diff --git a/generic/synth/prims.v b/generic/synth/prims.v
index 1148041..ca445e6 100644
--- a/generic/synth/prims.v
+++ b/generic/synth/prims.v
@@ -2,18 +2,27 @@
 
 module LUT #(
 	parameter K = 4,
-	parameter [2**K-1:0] INIT = 0,
+	parameter [2**K-1:0] INIT = 0
 ) (
 	input [K-1:0] I,
 	output Q
 );
-	assign Q = INIT[I];
+	wire [K-1:0] I_pd;
+
+	genvar ii;
+	generate
+		for (ii = 0; ii < K; ii = ii + 1'b1)
+			assign I_pd[ii] = (I[ii] === 1'bz) ? 1'b0 : I[ii];
+	endgenerate
+
+	assign Q = INIT[I_pd];
 endmodule
 
 module DFF (
 	input CLK, D,
 	output reg Q
 );
+	initial Q = 1'b0;
 	always @(posedge CLK)
 		Q <= D;
 endmodule
diff --git a/json/jsonwrite.cc b/json/jsonwrite.cc
index 3d3b70e..9723d3b 100644
--- a/json/jsonwrite.cc
+++ b/json/jsonwrite.cc
@@ -73,14 +73,17 @@
     PortType dir;
 };
 
-std::vector<PortGroup> group_ports(Context *ctx)
+std::vector<PortGroup> group_ports(Context *ctx, const std::unordered_map<IdString, PortInfo> &ports,
+                                   bool is_cell = false)
 {
     std::vector<PortGroup> groups;
     std::unordered_map<std::string, size_t> base_to_group;
-    for (auto &pair : ctx->ports) {
+    for (auto &pair : ports) {
         std::string name = pair.second.name.str(ctx);
         if ((name.back() != ']') || (name.find('[') == std::string::npos)) {
-            groups.push_back({name, {pair.first.index}, pair.second.type});
+            groups.push_back({name,
+                              {is_cell ? (pair.second.net ? pair.second.net->name.index : -1) : pair.first.index},
+                              pair.second.type});
         } else {
             int off1 = int(name.find_last_of('['));
             std::string basename = name.substr(0, off1);
@@ -95,26 +98,27 @@
             if (int(grp.bits.size()) <= index)
                 grp.bits.resize(index + 1, -1);
             NPNR_ASSERT(grp.bits.at(index) == -1);
-            grp.bits.at(index) = pair.second.net ? pair.second.net->name.index : pair.first.index;
+            grp.bits.at(index) = pair.second.net ? pair.second.net->name.index : (is_cell ? -1 : pair.first.index);
         }
     }
     return groups;
-};
+}
 
-std::string format_port_bits(const PortGroup &port)
+std::string format_port_bits(const PortGroup &port, int &dummy_idx)
 {
     std::stringstream s;
     s << "[ ";
     bool first = true;
-    for (auto bit : port.bits) {
-        if (!first)
-            s << ", ";
-        if (bit == -1)
-            s << "\"x\"";
-        else
-            s << bit;
-        first = false;
-    }
+    if (port.bits.size() != 1 || port.bits.at(0) != -1) // skip single disconnected ports
+        for (auto bit : port.bits) {
+            if (!first)
+                s << ", ";
+            if (bit == -1)
+                s << (++dummy_idx);
+            else
+                s << bit;
+            first = false;
+        }
     s << " ]";
     return s.str();
 }
@@ -122,6 +126,7 @@
 void write_module(std::ostream &f, Context *ctx)
 {
     auto val = ctx->attrs.find(ctx->id("module"));
+    int dummy_idx = int(ctx->idstring_idx_to_str->size()) + 1000;
     if (val != ctx->attrs.end())
         f << stringf("    %s: {\n", get_string(val->second.as_string()).c_str());
     else
@@ -134,14 +139,14 @@
     f << stringf("\n      },\n");
     f << stringf("      \"ports\": {");
 
-    auto ports = group_ports(ctx);
+    auto ports = group_ports(ctx, ctx->ports);
     bool first = true;
     for (auto &port : ports) {
         f << stringf("%s\n", first ? "" : ",");
         f << stringf("        %s: {\n", get_string(port.name).c_str());
         f << stringf("          \"direction\": \"%s\",\n",
                      port.dir == PORT_IN ? "input" : port.dir == PORT_INOUT ? "inout" : "output");
-        f << stringf("          \"bits\": %s\n", format_port_bits(port).c_str());
+        f << stringf("          \"bits\": %s\n", format_port_bits(port, dummy_idx).c_str());
         f << stringf("        }");
         first = false;
     }
@@ -151,6 +156,7 @@
     first = true;
     for (auto &pair : ctx->cells) {
         auto &c = pair.second;
+        auto cell_ports = group_ports(ctx, c->ports, true);
         f << stringf("%s\n", first ? "" : ",");
         f << stringf("        %s: {\n", get_name(c->name, ctx).c_str());
         f << stringf("          \"hide_name\": %s,\n", c->name.c_str(ctx)[0] == '$' ? "1" : "0");
@@ -163,24 +169,18 @@
         f << stringf("\n          },\n");
         f << stringf("          \"port_directions\": {");
         bool first2 = true;
-        for (auto &conn : c->ports) {
-            auto &p = conn.second;
-            std::string direction = (p.type == PORT_IN) ? "input" : (p.type == PORT_OUT) ? "output" : "inout";
+        for (auto &pg : cell_ports) {
+            std::string direction = (pg.dir == PORT_IN) ? "input" : (pg.dir == PORT_OUT) ? "output" : "inout";
             f << stringf("%s\n", first2 ? "" : ",");
-            f << stringf("            %s: \"%s\"", get_name(conn.first, ctx).c_str(), direction.c_str());
+            f << stringf("            %s: \"%s\"", get_string(pg.name).c_str(), direction.c_str());
             first2 = false;
         }
         f << stringf("\n          },\n");
         f << stringf("          \"connections\": {");
         first2 = true;
-        for (auto &conn : c->ports) {
-            auto &p = conn.second;
+        for (auto &pg : cell_ports) {
             f << stringf("%s\n", first2 ? "" : ",");
-            if (p.net)
-                f << stringf("            %s: [ %d ]", get_name(conn.first, ctx).c_str(), p.net->name.index);
-            else
-                f << stringf("            %s: [ ]", get_name(conn.first, ctx).c_str());
-
+            f << stringf("            %s: %s", get_string(pg.name).c_str(), format_port_bits(pg, dummy_idx).c_str());
             first2 = false;
         }
         f << stringf("\n          }\n");