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;