I am seeing Vivado crash at "Start Cross Boundary and Area Optimization" in synthesis, about a minute into starting synthesis.
With some liberal commenting of blocks out I was able to locate the fault to a custom IP of mine. Inside this IP is a module that computes the address of a buffer using a few combinatorial inputs. I cannot disclose the exact source but it has the structure:
reg [31:0] addr;
always @* begin
case (protocol)
PROTOCOL1: begin
if (yc_sel == 0) begin
addr <= base_mask | (buff_y_base + (fram_arhd_segment * MEM_BUFF_HALF));
end
end
PROTOCOL2: begin
if (yc_sel == 0) begin
addr <= base_mask | buff_y_base;
end
end
PROTOCOL3: begin
addr <= base_mask + MEM_COMP_BASE + (comp_use * MEM_COMP_SIZE);
end
default : begin
addr <= 32'hXXXXXXXX;
end
endcase
end
My intention with the default assigning to dont-care was to say to the synthesis/implementation tool that "this won't happen, so optimise the logic for it not happening to be irrelevant".
But later I noticed that I had mistakenly not included all conditions as yc_sel being nonzero would result in a non-assignment to addr. (The intention was in the future versions of the IP to support these.) However, adding in the missing conditions did not fix the crash.
I then changed the file to a SystemVerilog file and rewrote this as an always_comb block. This no longer crashes. (I am still getting used to SV, so it was not my natural first choice.)
Has anyone seen this before? Is there something wrong with what I've written, or is this just yet another Vivado bug?
I was misleading myself. In testing always_comb I left a statement in place to the effect of replacing the combinatorial output of that. Vivado still crashed with always_comb once I removed that statement. However, I found the actual cause. 'fram_arhd_segment' was typoed in a downstream module, which was causing the disconnected net to be optimised out. Why this led the synthesis process to crash is anyone's guess though, it's clearly a bug of some kind in the tool.
This code has non-blocking assignments in combinatorial block.
Have you considered:
wire [31:0] addr = (protocol==PROTOCOL1) ? base_mask | (buff_y_base + (fram_arhd_segment * MEM_BUFF_HALF)) :
(protocol==PROTOCOL2) ? base_mask | buff_y_base :
(protocol==PROTOCOL3) ? base_mask + MEM_COMP_BASE + (comp_use * MEM_COMP_SIZE) :
32'hXXXXXXXX;
A simple wire, identical in both styles of verilog.
The problem is '(yc_sel == 0)', since you are not clocking anything, Im not sure your coding here with combinational logic is a good choice. You could call this 'wire [31:0] address_select = .....' and later on say if (yc_sel == 0) addr <= address_select; where it is being clocked...
Also, if you want this addr to be driven by another se of logic, either tristate IO pins, or dangerous internal fpga logic, 32'hXXXXXXXX should be changed to 32'hZZZZZZZZ, though I do not know what you are trying to do. I reserve the 'x' for displaying an erroneous or unknown state for those simulating my code, not for actual FPGA functionality.
Also, remember, if you are creating IP for release, or more than 1 vendor of FPGA, you will need to test your code in other environments as they all reveal different degree of nuances of error and warning checking during compile.
Thanks.
I did consider the ternary operator, but felt it looked messier than the alternative of an always_comb/always mux block. I'll fix the non-blocking assignment, I've literally not used always_comb before this so still learning the ropes, as SystemVerilog is still new to me. (However I do recall trying both and Vivado still crashed.)
For clarity base_mask, buff_y_base etc all change very infrequently (external control, <100Hz update rate) and are latched on the same clock as the logic using the addr after a register stage. The purpose of this logic is to compute the base address of some array which is then incremented by another block until an end state is reached.
It only needs to work on a Xilinx device, so no vendor compatibility issues.
My thought with 32'hXXXXXXXX is that it tells the synthesis tool that I don't care what this output is. That state is unreachable (within normal operation) and if that does somehow get reached then there's probably a serious bug so it doesn't really matter. I therefore include it only to get the synthesis tool to stop worrying about a default condition and design the logic to output whatever makes the Karnaugh mapping smaller. I've used the same 'trick' on the AXI register slave which controls the interface to this block, the result is reading back unassigned registers usually returns an adjacent register or part thereof. It seems to simplify the multiplexing logic at no cost.
However messy the code may have been, the tools should not crash.
However messy the code may have been, the tools should not crash.
Exactly my thought. 'Error' during build is fine. An 'access violation' with absolutely no debug information, and no one else having encountered it before, is a royal PITA.
Annoyingly Xilinx have no option switch for 'extra debug' during logs (according to Xilinx support.) The official method for locating a crash like this is to black box things until the bug goes away. Commenting out stuff is the lazier approach but it got there eventually. Still, it shouldn't be necessary!
However messy the code may have been, the tools should not crash.
You are right. You have no idea how many time I could make Quartus crash having to work around internal bugs. Especially a divide by zero bug which even Modelsim isn't immune from.
I also hated Protel 98, too many arcs traces with the wrong dimensions and you loose everything.
Yep. Now I never had a crash with Xilinx ISE or Lattice Diamond that I can remember of. Not all tools are equally as buggy, but yes, complex software without bugs is rather uncommon.
Report this crash to Xilinx in the "support" forum
Why are you using non-blocking assignments in a combinatorial always block?