Merge pull request #14 from antmicro/xdc_port_index_fix

XDC: Fix port index parsing in get_ports command
diff --git a/xdc-plugin/Makefile b/xdc-plugin/Makefile
index e752504..a2ef6eb 100644
--- a/xdc-plugin/Makefile
+++ b/xdc-plugin/Makefile
@@ -26,4 +26,5 @@
 
 clean:
 	rm -f *.d *.o xdc.so
+	$(MAKE) -C tests clean
 
diff --git a/xdc-plugin/tests/Makefile b/xdc-plugin/tests/Makefile
index 328a5fa..89b2fb8 100644
--- a/xdc-plugin/tests/Makefile
+++ b/xdc-plugin/tests/Makefile
@@ -1,10 +1,16 @@
-TESTS = counter
+TESTS = counter \
+	port_indexes #Bus port indexes passed without curly braces
+
+counter_verify = $(call compare_json, counter)
+port_indexes_verify = $(call compare_json, port_indexes) && test $$(grep "'unknown' proc command handler" port_indexes.txt | wc -l) -eq 2
 
 all: $(TESTS)
 
+compare_json = python compare_output_json.py --json $(1).json --golden $(1)_golden.json
+
 define test_tpl =
 $(1): $(1).json
-	@python compare_output_json.py --json $$< --golden $(1)_golden.json; \
+	$$($(1)_verify);
 	RETVAL=$$$$? ; \
 	if [ $$$$RETVAL -eq 0 ]; then \
 		echo "$(1) PASS"; \
@@ -18,7 +24,7 @@
 	PART_JSON=xc7a35tcsg324-1.json \
 	OUT_JSON=$(1).json \
 	INPUT_XDC_FILE=$(1).xdc \
-	yosys -p "tcl synth.tcl" $$< -l yosys.log
+	yosys -p "tcl $(1).tcl" $$< -l yosys.log
 
 update_$(1): $(1).json
 	@python compare_output_json.py --json $$< --golden $(1)_golden.json --update
diff --git a/xdc-plugin/tests/synth.tcl b/xdc-plugin/tests/counter.tcl
similarity index 100%
rename from xdc-plugin/tests/synth.tcl
rename to xdc-plugin/tests/counter.tcl
diff --git a/xdc-plugin/tests/port_indexes.tcl b/xdc-plugin/tests/port_indexes.tcl
new file mode 100644
index 0000000..ac05de3
--- /dev/null
+++ b/xdc-plugin/tests/port_indexes.tcl
@@ -0,0 +1,33 @@
+yosys -import
+plugin -i xdc
+#Import the commands from the plugins to the tcl interpreter
+yosys -import
+
+# -flatten is used to ensure that the output eblif has only one module.
+# Some of symbiflow expects eblifs with only one module.
+synth_xilinx -vpr -flatten -abc9 -nosrl -noclkbuf -nodsp
+
+if {[info procs unknown] != ""} {
+	rename unknown ""
+}
+proc unknown args {return "'unknown' proc command handler"}
+set fp [open "port_indexes.txt" "w"]
+if {[catch {invalid command} result]} {
+	close $fp
+	error "Command should be handled by the 'unknown' proc"
+} else {
+	puts $fp $result
+}
+#Read the design constraints
+read_xdc -part_json $::env(PART_JSON) $::env(INPUT_XDC_FILE)
+
+if {[catch {invalid command} result]} {
+	close $fp
+	error "Command should be handled by the 'unknown' proc"
+} else {
+	puts $fp $result
+}
+close $fp
+
+# Write the design in JSON format.
+write_json $::env(OUT_JSON)
diff --git a/xdc-plugin/tests/port_indexes.v b/xdc-plugin/tests/port_indexes.v
new file mode 100644
index 0000000..2ca86e2
--- /dev/null
+++ b/xdc-plugin/tests/port_indexes.v
@@ -0,0 +1,67 @@
+module top (
+	input  clk,
+	output [3:0] led,
+	inout out_a,
+	output [1:0] out_b
+);
+
+	wire LD6, LD7, LD8, LD9;
+	wire inter_wire, inter_wire_2;
+	localparam BITS = 1;
+	localparam LOG2DELAY = 25;
+
+	reg [BITS+LOG2DELAY-1:0] counter = 0;
+
+	always @(posedge clk) begin
+		counter <= counter + 1;
+	end
+	assign led[1] = inter_wire;
+	assign inter_wire = inter_wire_2;
+	assign {LD9, LD8, LD7, LD6} = counter >> LOG2DELAY;
+	OBUF #(
+		.IOSTANDARD("LVCMOS33"),
+		.SLEW("SLOW")
+	) OBUF_6(.I(LD6), .O(led[0]));
+	OBUF #(
+		.IOSTANDARD("LVCMOS33"),
+		.SLEW("SLOW")
+	) OBUF_7(.I(LD7), .O(inter_wire_2));
+	OBUF #(
+		.IOSTANDARD("LVCMOS33"),
+		.SLEW("SLOW")
+	) OBUF_OUT(.I(LD7), .O(out_a));
+	bottom bottom_inst(.I(LD8), .O(led[2]), .OB(out_b));
+	bottom_intermediate bottom_intermediate_inst(.I(LD9), .O(led[3]));
+endmodule
+
+module bottom_intermediate (
+	input I,
+	output O
+);
+	wire bottom_intermediate_wire;
+	assign O = bottom_intermediate_wire;
+	OBUF #(
+		.IOSTANDARD("LVCMOS33"),
+		.SLEW("SLOW")
+	) OBUF_8(.I(I), .O(bottom_intermediate_wire));
+endmodule
+
+module bottom (
+	input I,
+	output [1:0] OB,
+	output O
+);
+	OBUF #(
+		.IOSTANDARD("LVCMOS33"),
+		.SLEW("SLOW")
+	) OBUF_9(.I(I), .O(O));
+	OBUF #(
+		.IOSTANDARD("LVCMOS33"),
+		.SLEW("SLOW")
+	) OBUF_10(.I(I), .O(OB[0]));
+	OBUF #(
+		.IOSTANDARD("LVCMOS33"),
+		.SLEW("SLOW")
+	) OBUF_11(.I(I), .O(OB[1]));
+endmodule
+
diff --git a/xdc-plugin/tests/port_indexes.xdc b/xdc-plugin/tests/port_indexes.xdc
new file mode 100644
index 0000000..48202f5
--- /dev/null
+++ b/xdc-plugin/tests/port_indexes.xdc
@@ -0,0 +1,30 @@
+#set_property LOC R2 [get_ports led]
+#OBUF_6
+set_property DRIVE 12 [get_ports led[0]]
+#OBUF_7
+set_property IN_TERM UNTUNED_SPLIT_40 [get_ports led[1]]
+set_property SLEW FAST [get_ports led[1]]
+set_property IOSTANDARD SSTL135 [get_ports led[1]]
+#OBUF_OUT
+set_property IN_TERM UNTUNED_SPLIT_50 [get_ports out_a]
+set_property SLEW FAST [get_ports out_a]
+set_property IOSTANDARD LVCMOS33 [get_ports out_a]
+#bottom_inst.OBUF_10
+set_property SLEW SLOW [get_ports out_b[0]]
+set_property IOSTANDARD LVCMOS18 [get_ports out_b[0]]
+#bottom_inst.OBUF_11
+set_property DRIVE 4 [get_ports out_b[1]]
+set_property SLEW FAST [get_ports out_b[1]]
+set_property IOSTANDARD LVCMOS25 [get_ports out_b[1]]
+#bottom_inst.OBUF_9
+set_property SLEW FAST [get_ports led[2]]
+set_property IOSTANDARD DIFF_SSTL135 [get_ports led[2]]
+#bottom_intermediate_inst.OBUF_8
+set_property DRIVE 16 [get_ports led[3]]
+set_property IOSTANDARD SSTL135 [get_ports led[3]]
+#set_property INTERNAL_VREF 0.600 [get_iobanks 14]
+#set_property INTERNAL_VREF 0.675 [get_iobanks 15]
+#set_property INTERNAL_VREF 0.750 [get_iobanks 16]
+#set_property INTERNAL_VREF 0.900 [get_iobanks 34]
+#set_property INTERNAL_VREF 0.900 [get_iobanks 35]
+
diff --git a/xdc-plugin/tests/port_indexes_golden.json b/xdc-plugin/tests/port_indexes_golden.json
new file mode 100644
index 0000000..25e1234
--- /dev/null
+++ b/xdc-plugin/tests/port_indexes_golden.json
@@ -0,0 +1,35 @@
+{
+  "OBUF_6": {
+    "DRIVE": "12",
+    "IOSTANDARD": "LVCMOS33",
+    "SLEW": "SLOW"
+  },
+  "OBUF_7": {
+    "IN_TERM": "UNTUNED_SPLIT_40",
+    "IOSTANDARD": "SSTL135",
+    "SLEW": "FAST"
+  },
+  "OBUF_OUT": {
+    "IN_TERM": "UNTUNED_SPLIT_50",
+    "IOSTANDARD": "LVCMOS33",
+    "SLEW": "FAST"
+  },
+  "bottom_inst.OBUF_10": {
+    "IOSTANDARD": "LVCMOS18",
+    "SLEW": "SLOW"
+  },
+  "bottom_inst.OBUF_11": {
+    "DRIVE": "4",
+    "IOSTANDARD": "LVCMOS25",
+    "SLEW": "FAST"
+  },
+  "bottom_inst.OBUF_9": {
+    "IOSTANDARD": "DIFF_SSTL135",
+    "SLEW": "FAST"
+  },
+  "bottom_intermediate_inst.OBUF_8": {
+    "DRIVE": "16",
+    "IOSTANDARD": "SSTL135",
+    "SLEW": "SLOW"
+  }
+}
\ No newline at end of file
diff --git a/xdc-plugin/xdc.cc b/xdc-plugin/xdc.cc
index 673c806..cf821d4 100644
--- a/xdc-plugin/xdc.cc
+++ b/xdc-plugin/xdc.cc
@@ -375,7 +375,6 @@
                 if (args.size() < 2) {
                         log_cmd_error("Missing script file.\n");
 		}
-                Tcl_Interp *interp = yosys_get_tcl_interp();
 		size_t argidx = 1;
 		bank_tiles.clear();
 		if (args[argidx] == "-part_json" && argidx + 1 < args.size()) {
@@ -385,9 +384,36 @@
 		extra_args(f, filename, args, argidx);
 		std::string content{std::istreambuf_iterator<char>(*f), std::istreambuf_iterator<char>()};
 		log("%s\n", content.c_str());
+
+		// According to page 6 of UG903 XDC is tcl, hence quoting of bracketed numbers,
+		// such as bus indexes, is required. For example "signal[5]" would be typically
+		// expanded to the concatenation of the string "signal" and result of the function call "5"
+		// with no arguments. Therefore in TCL the signal indices have to be wrapped in curly braces
+		// e.g "{signal[5]}" in order for the interpreter to not perform any variable substitution
+		// or function calls on the wrapped content.
+		//
+		// Nevertheless, it's quite common for EDA tools to allow for specifying signal indices
+		// (e.g. "signal[5]") without using non-expanding quotes.
+		// Possible TCL implementations of such a feature include registering a TCL command
+		// for each integer which returns itself but surrounded with brackets or using the 'unknown'
+		// command which is invoked by the Tcl interpreter whenever a script tries to invoke a command
+		// that does not exist. In the XDC plugin the latter approach is used, however it's limited to
+		// the 'read_xdc' command, hence the 'unknown' command works solely on the content of the XDC file.
+		//
+		// In this implementation the signal "signal[5]" is expanded in TCL to the concatenation of a string
+		// and function call, however this time the handling of the non-existent command '5' is passed by
+		// the interpreter to the 'unknown' command which returns a string that consists of the indice
+		// integer surrounded by square brackets, i.e. "[5]", effectively expanding the signal to "signal[5]"
+		// string.
+		//
+		Tcl_Interp* interp = yosys_get_tcl_interp();
+		Tcl_Eval(interp, "rename unknown _original_unknown");
+		Tcl_Eval(interp, "proc unknown args { return \\[[lindex $args 0]\\] }");
                 if (Tcl_EvalFile(interp, args[argidx].c_str()) != TCL_OK) {
                         log_cmd_error("TCL interpreter returned an error: %s\n", Tcl_GetStringResult(interp));
 		}
+		Tcl_Eval(interp, "rename unknown \"\"");
+		Tcl_Eval(interp, "rename _original_unknown unknown");
 	}
 	const BankTilesMap& get_bank_tiles() {
 		return bank_tiles;