diff --git a/src/vhdl_backend.cc b/src/vhdl_backend.cc index 11d9cf3..2b9a4ca 100644 --- a/src/vhdl_backend.cc +++ b/src/vhdl_backend.cc @@ -214,8 +214,9 @@ std::string next_auto_id() return stringf("%s%0*d", auto_prefix.c_str(), auto_name_digits, auto_name_offset + auto_name_counter++); } -// Collected signal declarations from FF cells (emitted before "begin") -std::vector ff_signal_decls; +// Auxiliary signal declarations (FF internals, split-Y temporaries, and +// bool-to-vector intermediates) emitted before "begin" in the architecture. +std::vector aux_signal_decls; // Check if a character needs extended identifier escaping in VHDL bool char_needs_vhdl_escape(char c) @@ -591,6 +592,35 @@ void dump_process_sens(std::ostream &f, std::string indent, std::vector 1, VHDL-93 s7.3.2 forbids conditional expressions inside +// aggregates, so an intermediate std_logic signal is introduced: +// bool_sig <= '1' when COND else '0'; +// Y <= (0 => bool_sig, others => '0'); +// +// cond must be a complete VHDL boolean expression (no surrounding 'when'/'else'). +void dump_bool_assign(std::ostream &f, std::string indent, + const RTLIL::SigSpec &y, const std::string &cond) +{ + if (GetSize(y) == 1) { + f << indent; + dump_sigspec(f, y); + f << " <= '1' when " << cond << " else '0';\n"; + } else { + std::string bool_sig = next_auto_id(); + aux_signal_decls.push_back( + stringf("signal %s : std_logic;", bool_sig.c_str())); + f << indent << bool_sig << " <= '1' when " << cond << " else '0';\n"; + f << indent; + dump_sigspec(f, y); + f << " <= (0 => " << bool_sig << ", others => '0');\n"; + } +} + bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) { // Gate-level NOT @@ -724,13 +754,15 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) if (a_width == y_width) { dump_sigspec(f, cell->getPort(ID::A)); } else { - f << "std_logic_vector(resize("; + // Use arith_open/close so y_width==1 produces std_logic (not + // std_logic_vector(0 downto 0) which would be a type mismatch). + f << arith_open(y_width) << "resize("; if (a_signed) f << "signed("; else f << "unsigned("; dump_sigspec(f, cell->getPort(ID::A)); - f << "), " << y_width << "))"; + f << "), " << y_width << ")" << arith_close(y_width); } f << ";\n"; return true; @@ -809,11 +841,11 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) op = "xnor"; f << indent << "-- reduce_" << op << "\n"; - f << indent; - dump_sigspec(f, cell->getPort(ID::Y)); if (a_width == 1) { // Single bit: just pass through (possibly with inversion for xnor) + f << indent; + dump_sigspec(f, cell->getPort(ID::Y)); f << " <= "; if (cell->type == ID($reduce_xnor)) f << "not "; @@ -828,107 +860,71 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) } f << ";\n"; } else { - // Use process-style: generate a temp via function - // For now, use a generate-friendly concurrent form: - // Y <= '1' when (and_reduce condition) else '0' - f << " <= "; - if (y_width > 1) - f << "(0 => "; - - f << "'1' when "; + std::stringstream cond; if (cell->type == ID($reduce_and)) { - f << "("; - dump_sigspec(f, cell->getPort(ID::A)); - f << ") = " << vhdl_ones_const(a_width); + cond << "("; + dump_sigspec(cond, cell->getPort(ID::A)); + cond << ") = " << vhdl_ones_const(a_width); } else if (cell->type.in(ID($reduce_or), ID($reduce_bool))) { - f << "("; - dump_sigspec(f, cell->getPort(ID::A)); - f << ") /= " << vhdl_zero_const(a_width); + cond << "("; + dump_sigspec(cond, cell->getPort(ID::A)); + cond << ") /= " << vhdl_zero_const(a_width); } else if (cell->type == ID($reduce_xor)) { - // XOR reduce: check if number of 1-bits is odd - // Use a helper: xor of all bits - f << "("; + cond << "("; for (int i = 0; i < a_width; i++) { if (i > 0) - f << " xor "; - dump_sigspec(f, cell->getPort(ID::A)); - f << "(" << i << ")"; + cond << " xor "; + dump_sigspec(cond, cell->getPort(ID::A)); + cond << "(" << i << ")"; } - f << ") = '1'"; + cond << ") = '1'"; } else { // reduce_xnor - f << "("; + cond << "("; for (int i = 0; i < a_width; i++) { if (i > 0) - f << " xor "; - dump_sigspec(f, cell->getPort(ID::A)); - f << "(" << i << ")"; + cond << " xor "; + dump_sigspec(cond, cell->getPort(ID::A)); + cond << "(" << i << ")"; } - f << ") = '0'"; + cond << ") = '0'"; } - f << " else '0'"; - if (y_width > 1) - f << ", others => '0')"; - f << ";\n"; + dump_bool_assign(f, indent, cell->getPort(ID::Y), cond.str()); } return true; } // $logic_not -- logical NOT (Y = A == 0) if (cell->type == ID($logic_not)) { - int y_width = GetSize(cell->getPort(ID::Y)); - f << indent; - dump_sigspec(f, cell->getPort(ID::Y)); - f << " <= "; - if (y_width > 1) - f << "(0 => "; - f << "'1' when "; - dump_sigspec(f, cell->getPort(ID::A)); + std::stringstream cond; + dump_sigspec(cond, cell->getPort(ID::A)); if (GetSize(cell->getPort(ID::A)) == 1) - f << " = '0'"; - else { - f << " = " << vhdl_zero_const(GetSize(cell->getPort(ID::A))); - } - f << " else '0'"; - if (y_width > 1) - f << ", others => '0')"; - f << ";\n"; + cond << " = '0'"; + else + cond << " = " << vhdl_zero_const(GetSize(cell->getPort(ID::A))); + dump_bool_assign(f, indent, cell->getPort(ID::Y), cond.str()); return true; } // $logic_and, $logic_or if (cell->type.in(ID($logic_and), ID($logic_or))) { - int y_width = GetSize(cell->getPort(ID::Y)); bool is_and = cell->type == ID($logic_and); - - f << indent; - dump_sigspec(f, cell->getPort(ID::Y)); - f << " <= "; - if (y_width > 1) - f << "(0 => "; - f << "'1' when ("; - + std::stringstream cond; + cond << "("; // A != 0 - dump_sigspec(f, cell->getPort(ID::A)); + dump_sigspec(cond, cell->getPort(ID::A)); if (GetSize(cell->getPort(ID::A)) == 1) - f << " = '1'"; - else { - f << " /= " << vhdl_zero_const(GetSize(cell->getPort(ID::A))); - } - - f << (is_and ? " and " : " or "); - + cond << " = '1'"; + else + cond << " /= " << vhdl_zero_const(GetSize(cell->getPort(ID::A))); + cond << (is_and ? " and " : " or "); // B != 0 - dump_sigspec(f, cell->getPort(ID::B)); + dump_sigspec(cond, cell->getPort(ID::B)); if (GetSize(cell->getPort(ID::B)) == 1) - f << " = '1'"; - else { - f << " /= " << vhdl_zero_const(GetSize(cell->getPort(ID::B))); - } - - f << ") else '0'"; - if (y_width > 1) - f << ", others => '0')"; - f << ";\n"; + cond << " = '1'"; + else + cond << " /= " << vhdl_zero_const(GetSize(cell->getPort(ID::B))); + cond << ")"; + dump_bool_assign(f, indent, cell->getPort(ID::Y), cond.str()); return true; } @@ -1080,7 +1076,6 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) // Comparison operators: $lt, $le, $eq, $ne, $ge, $gt, $eqx, $nex if (cell->type.in(ID($lt), ID($le), ID($eq), ID($ne), ID($ge), ID($gt), ID($eqx), ID($nex))) { - int y_width = GetSize(cell->getPort(ID::Y)); bool a_signed = cell->getParam(ID::A_SIGNED).as_bool(); bool b_signed = cell->getParam(ID::B_SIGNED).as_bool(); bool use_signed = a_signed && b_signed; @@ -1099,34 +1094,25 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) else op = ">"; - f << indent; - dump_sigspec(f, cell->getPort(ID::Y)); - f << " <= "; - if (y_width > 1) - f << "(0 => "; - int a_width = cell->getParam(ID::A_WIDTH).as_int(); int b_width = cell->getParam(ID::B_WIDTH).as_int(); - f << "'1' when "; + std::stringstream cond; if (a_width == 1 && b_width == 1 && cell->type.in(ID($eq), ID($eqx), ID($ne), ID($nex))) { // 1-bit equality/inequality: compare as std_logic directly - dump_sigspec(f, cell->getPort(ID::A)); - f << " " << op << " "; - dump_sigspec(f, cell->getPort(ID::B)); + dump_sigspec(cond, cell->getPort(ID::A)); + cond << " " << op << " "; + dump_sigspec(cond, cell->getPort(ID::B)); } else if (use_signed) { - dump_sigspec_signed(f, cell->getPort(ID::A)); - f << " " << op << " "; - dump_sigspec_signed(f, cell->getPort(ID::B)); + dump_sigspec_signed(cond, cell->getPort(ID::A)); + cond << " " << op << " "; + dump_sigspec_signed(cond, cell->getPort(ID::B)); } else { - dump_sigspec_unsigned(f, cell->getPort(ID::A)); - f << " " << op << " "; - dump_sigspec_unsigned(f, cell->getPort(ID::B)); + dump_sigspec_unsigned(cond, cell->getPort(ID::A)); + cond << " " << op << " "; + dump_sigspec_unsigned(cond, cell->getPort(ID::B)); } - f << " else '0'"; - if (y_width > 1) - f << ", others => '0')"; - f << ";\n"; + dump_bool_assign(f, indent, cell->getPort(ID::Y), cond.str()); return true; } @@ -1269,9 +1255,9 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) init_str = " := " + init_ss.str(); } if (ff.width == 1) - ff_signal_decls.push_back(stringf("signal %s : std_logic%s;", sig_name.c_str(), init_str.c_str())); + aux_signal_decls.push_back(stringf("signal %s : std_logic%s;", sig_name.c_str(), init_str.c_str())); else - ff_signal_decls.push_back( + aux_signal_decls.push_back( stringf("signal %s : std_logic_vector(%d downto 0)%s;", sig_name.c_str(), ff.width - 1, init_str.c_str())); f << indent << "-- FF " << id(cell->name) << "\n"; @@ -1683,7 +1669,7 @@ void dump_memory(std::ostream &f, std::string indent, Mem &mem) } else { // Sync read -- clocked process std::string temp_name = next_auto_id(); - ff_signal_decls.push_back(stringf("signal %s : std_logic_vector(%d downto 0);", temp_name.c_str(), mem.width - 1)); + aux_signal_decls.push_back(stringf("signal %s : std_logic_vector(%d downto 0);", temp_name.c_str(), mem.width - 1)); f << indent << "process("; dump_sigspec(f, port.clk); @@ -1827,7 +1813,7 @@ void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell) orig_y = cell->getPort(ID::Y); int width = orig_y.size(); std::string y_tmp = next_auto_id(); - ff_signal_decls.push_back(stringf("signal %s : %s;", y_tmp.c_str(), vhdl_type_str(width).c_str())); + aux_signal_decls.push_back(stringf("signal %s : %s;", y_tmp.c_str(), vhdl_type_str(width).c_str())); // Create a temporary wire to hold the Y output RTLIL::Wire *tmp_wire = active_module->addWire(RTLIL::IdString("\\" + y_tmp), width); @@ -1899,7 +1885,7 @@ void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell) if (is_output || is_input) { std::string tmp = next_auto_id(); int width = it->second.size(); - ff_signal_decls.push_back(stringf("signal %s : %s;", tmp.c_str(), vhdl_type_str(width).c_str())); + aux_signal_decls.push_back(stringf("signal %s : %s;", tmp.c_str(), vhdl_type_str(width).c_str())); port_intermediates.push_back({tmp, {it->second, is_output}}); } } @@ -2029,7 +2015,7 @@ void dump_expr_assign(std::ostream &f, std::string indent, const RTLIL::SigSpec // Multi-chunk target: need intermediate signal std::string tmp = next_auto_id(); int width = left.size(); - ff_signal_decls.push_back(stringf("signal %s : %s;", tmp.c_str(), vhdl_type_str(width).c_str())); + aux_signal_decls.push_back(stringf("signal %s : %s;", tmp.c_str(), vhdl_type_str(width).c_str())); f << indent << tmp << " <= " << expr << ";\n"; // Split into per-chunk assignments int offset = 0; @@ -2383,7 +2369,7 @@ void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module, RTL // Pre-pass: dump cells, memories, and connections to a buffer // to collect FF/memory signal declarations for the declarative region - ff_signal_decls.clear(); + aux_signal_decls.clear(); mem_type_decls.clear(); std::stringstream body_buf; @@ -2401,7 +2387,7 @@ void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module, RTL f << indent << " " << decl << "\n"; // Emit collected FF signal declarations in the architecture declarative region - for (auto &decl : ff_signal_decls) + for (auto &decl : aux_signal_decls) f << indent << " " << decl << "\n"; f << indent << "begin\n"; diff --git a/testsuite/write_vhdl/issue227/t_combined.v b/testsuite/write_vhdl/issue227/t_combined.v new file mode 100644 index 0000000..6fce009 --- /dev/null +++ b/testsuite/write_vhdl/issue227/t_combined.v @@ -0,0 +1,36 @@ +// t_combined.v -- multiple independent boolean cells feeding one output +// +// This is the structural test for the key property of the fix: +// N independent boolean cells => exactly N std_logic intermediates. +// +// Three independent boolean cells (distinct inputs, no CSE possible), +// OR'd together with a plain signal and a constant into a single 4-bit +// output. Each boolean cell has Y_WIDTH=4 and must get its own +// std_logic intermediate. The plain signal and constant require none. +// +// A broken "combined expression" fix would produce one intermediate +// (or none) where three are required. The test asserts the exact count. +// +// Expected RTLIL: 3 x $logic_and/or/not at Y_WIDTH=4, 4 x $or at Y_WIDTH=4. +// Expected VHDL after fix: exactly 3 'signal nXX : std_logic;' declarations. +module t_combined ( + input wire [3:0] vec_a, + input wire bool_c, bool_d, bool_e, bool_f, + output wire [3:0] o +); + // (bool_c && bool_d): $logic_and Y_WIDTH=4 -- intermediate required + // (bool_d || bool_e): $logic_or Y_WIDTH=4 -- intermediate required + // !bool_f: $logic_not Y_WIDTH=4 -- intermediate required + // vec_a: plain signal -- no intermediate + // 4'b1010: constant -- no intermediate + // + // Each boolean uses distinct inputs so Yosys cannot merge any two: + // and: bool_c, bool_d + // or: bool_d, bool_e (bool_d shared with and -- but different cell type) + // not: bool_f (unique input) + assign o = (bool_c && bool_d) + | (bool_d || bool_e) + | !bool_f + | vec_a + | 4'b1010; +endmodule diff --git a/testsuite/write_vhdl/issue227/t_compare.v b/testsuite/write_vhdl/issue227/t_compare.v new file mode 100644 index 0000000..c204d08 --- /dev/null +++ b/testsuite/write_vhdl/issue227/t_compare.v @@ -0,0 +1,35 @@ +// t_compare.v -- comparison cells ($eq, $ne, $lt, $eqx) with y_width > 1 +// +// Each cell type is exercised at two output widths: +// y_width=4 (broken path): must produce one std_logic intermediate each +// y_width=1 (control): must stay as direct '1' when COND else '0' +// +// Operand pairs are rotated between y_width=4 and y_width=1 outputs so +// Yosys cannot merge them via CSE. +// +// $eqx (casex equality, generated by ===) has zero prior test coverage. +// $ne, $lt cover the ne/lt/le/ge/gt code path (all share the same template). +// +// Expected RTLIL: 4 x comparison cells at Y_WIDTH=4, 4 at Y_WIDTH=1. +// Expected VHDL after fix: 4 new 'signal nXX : std_logic;' declarations. +module t_compare ( + input wire [3:0] vec_a, vec_b, vec_c, + output wire [3:0] o_eq4, // $eq y_width=4 (broken before fix) + output wire [3:0] o_ne4, // $ne y_width=4 (broken before fix) + output wire [3:0] o_lt4, // $lt y_width=4 (broken before fix) + output wire [3:0] o_eqx4, // $eqx y_width=4 (broken before fix) + output wire o_eq1, // $eq y_width=1 (control: must be unchanged) + output wire o_ne1, // $ne y_width=1 (control: must be unchanged) + output wire o_lt1, // $lt y_width=1 (control: must be unchanged) + output wire o_eqx1 // $eqx y_width=1 (control: must be unchanged) +); + assign o_eq4 = (vec_a == vec_b); // $eq Y_WIDTH=4 + assign o_ne4 = (vec_b != vec_c); // $ne Y_WIDTH=4 + assign o_lt4 = (vec_a < vec_c); // $lt Y_WIDTH=4 + assign o_eqx4 = (vec_a === vec_b); // $eqx Y_WIDTH=4 + + assign o_eq1 = (vec_a == vec_c); // $eq Y_WIDTH=1 -- different pair + assign o_ne1 = (vec_a != vec_b); // $ne Y_WIDTH=1 -- different pair + assign o_lt1 = (vec_b < vec_c); // $lt Y_WIDTH=1 -- different pair + assign o_eqx1 = (vec_b === vec_c); // $eqx Y_WIDTH=1 -- different pair +endmodule diff --git a/testsuite/write_vhdl/issue227/t_exact.v b/testsuite/write_vhdl/issue227/t_exact.v new file mode 100644 index 0000000..c841538 --- /dev/null +++ b/testsuite/write_vhdl/issue227/t_exact.v @@ -0,0 +1,14 @@ +// t_exact.v -- exact reproduction of ghdl-yosys-plugin issue #227 +// +// write_vhdl must produce valid VHDL-93 for o0 (y_width=3). +// o1 is the same $logic_and but y_width=1; it must be unchanged by any fix. +module t_exact ( + input wire [2:0] i0, + input wire i1, + input wire i2, + output wire [2:0] o0, // $logic_and result zero-extended to 3 bits (broken) + output wire o1 // $logic_and result 1 bit (control: must stay correct) +); + assign o0 = i0 | (i1 && i2); // y_width=3: was broken + assign o1 = i1 && i2; // y_width=1: must remain a direct conditional +endmodule diff --git a/testsuite/write_vhdl/issue227/t_logic.v b/testsuite/write_vhdl/issue227/t_logic.v new file mode 100644 index 0000000..42bc9e2 --- /dev/null +++ b/testsuite/write_vhdl/issue227/t_logic.v @@ -0,0 +1,29 @@ +// t_logic.v -- $logic_and, $logic_or, $logic_not with y_width > 1 +// +// Each cell type is exercised at two output widths: +// y_width=4 (broken path): must produce one std_logic intermediate each +// y_width=1 (control): must stay as direct '1' when COND else '0' +// +// Inputs are rotated between the y_width=4 and y_width=1 outputs so Yosys +// cannot merge them via CSE. +// +// Expected RTLIL: 3 x $logic_and/or/not at Y_WIDTH=4, 3 at Y_WIDTH=1. +// Expected VHDL after fix: 3 new 'signal nXX : std_logic;' declarations, +// 3 existing 'signal nXX : std_logic;' for the y_width=1 internal wires. +module t_logic ( + input wire bool_c, bool_d, bool_e, bool_f, + output wire [3:0] o_and4, // $logic_and y_width=4 (broken before fix) + output wire [3:0] o_or4, // $logic_or y_width=4 (broken before fix) + output wire [3:0] o_not4, // $logic_not y_width=4 (broken before fix) + output wire o_and1, // $logic_and y_width=1 (control: must be unchanged) + output wire o_or1, // $logic_or y_width=1 (control: must be unchanged) + output wire o_not1 // $logic_not y_width=1 (control: must be unchanged) +); + assign o_and4 = bool_c && bool_d; // $logic_and Y_WIDTH=4 + assign o_or4 = bool_c || bool_e; // $logic_or Y_WIDTH=4 + assign o_not4 = !bool_d; // $logic_not Y_WIDTH=4 + + assign o_and1 = bool_e && bool_f; // $logic_and Y_WIDTH=1 -- different inputs + assign o_or1 = bool_d || bool_f; // $logic_or Y_WIDTH=1 -- different inputs + assign o_not1 = !bool_f; // $logic_not Y_WIDTH=1 -- different input +endmodule diff --git a/testsuite/write_vhdl/issue227/t_pos_ext.v b/testsuite/write_vhdl/issue227/t_pos_ext.v new file mode 100644 index 0000000..1da4592 --- /dev/null +++ b/testsuite/write_vhdl/issue227/t_pos_ext.v @@ -0,0 +1,25 @@ +// t_pos_ext.v -- exercises $pos (zero/sign extension) cells. +// +// The $pos handler had a bug: the a_width != y_width branch emitted +// Y <= std_logic_vector(resize(unsigned(A), N)); +// which has type std_logic_vector(N-1 downto 0). For y_width==1 this is +// std_logic_vector(0 downto 0) assigned to a std_logic target -- a GHDL +// type mismatch. The fix uses arith_open/arith_close as every other +// arithmetic cell does. +// +// How to generate $pos cells from Verilog: +// unary + on a signed value forces the frontend to emit a $pos cell. +// The Y_WIDTH is set to the declared output width. +// +// Note: $pos with y_width==1 does not appear to be generated by the Verilog +// frontend (width mismatches to 1-bit outputs are resolved as RTLIL connect +// slices). The y_width > 1 path exercises the same arith_open/close code +// path and confirms the fix is self-consistent. +module t_pos_ext ( + input wire signed [3:0] s4, // 4-bit signed input + output wire [7:0] o_sext, // sign-extend s4 4->8: $pos A_SIGNED=1 Y=8 + output wire [3:0] o_id // identity: $pos a_width==y_width (no-op path) +); + assign o_sext = +s4; // unary + on signed: $pos A_SIGNED=1 A_WIDTH=4 Y_WIDTH=8 + assign o_id = +s4; // same width: $pos A_WIDTH=4 Y_WIDTH=4 (identity path) +endmodule diff --git a/testsuite/write_vhdl/issue227/t_reduce.v b/testsuite/write_vhdl/issue227/t_reduce.v new file mode 100644 index 0000000..7f0c6ef --- /dev/null +++ b/testsuite/write_vhdl/issue227/t_reduce.v @@ -0,0 +1,35 @@ +// t_reduce.v -- $reduce_and, $reduce_or, $reduce_xor, $reduce_xnor with y_width > 1 +// +// Each cell type is exercised at two output widths: +// y_width=4 (broken path): must produce one std_logic intermediate each +// y_width=1 (control): must stay as direct '1' when COND else '0' +// +// vec_a drives the y_width=4 outputs; vec_b drives the y_width=1 controls +// so Yosys cannot merge them via CSE. +// +// $reduce_xnor has zero prior test coverage at any width. +// +// Expected RTLIL: 4 x reduce_* at Y_WIDTH=4, 4 at Y_WIDTH=1. +// Expected VHDL after fix: 4 new 'signal nXX : std_logic;' declarations. +module t_reduce ( + input wire [3:0] vec_a, + input wire [3:0] vec_b, + output wire [3:0] o_and4, // $reduce_and y_width=4 (broken before fix) + output wire [3:0] o_or4, // $reduce_or y_width=4 (broken before fix) + output wire [3:0] o_xor4, // $reduce_xor y_width=4 (broken before fix) + output wire [3:0] o_xnor4, // $reduce_xnor y_width=4 (broken before fix) + output wire o_and1, // $reduce_and y_width=1 (control: must be unchanged) + output wire o_or1, // $reduce_or y_width=1 (control: must be unchanged) + output wire o_xor1, // $reduce_xor y_width=1 (control: must be unchanged) + output wire o_xnor1 // $reduce_xnor y_width=1 (control: must be unchanged) +); + assign o_and4 = &vec_a; // $reduce_and Y_WIDTH=4 + assign o_or4 = |vec_a; // $reduce_or Y_WIDTH=4 + assign o_xor4 = ^vec_a; // $reduce_xor Y_WIDTH=4 + assign o_xnor4 = ~^vec_a; // $reduce_xnor Y_WIDTH=4 + + assign o_and1 = &vec_b; // $reduce_and Y_WIDTH=1 -- different input + assign o_or1 = |vec_b; // $reduce_or Y_WIDTH=1 -- different input + assign o_xor1 = ^vec_b; // $reduce_xor Y_WIDTH=1 -- different input + assign o_xnor1 = ~^vec_b; // $reduce_xnor Y_WIDTH=1 -- different input +endmodule diff --git a/testsuite/write_vhdl/issue227/t_vhdl.vhd b/testsuite/write_vhdl/issue227/t_vhdl.vhd new file mode 100644 index 0000000..1818d30 --- /dev/null +++ b/testsuite/write_vhdl/issue227/t_vhdl.vhd @@ -0,0 +1,62 @@ +-- t_vhdl.vhd -- VHDL-sourced round-trip test for write_vhdl +-- +-- Exercises three paths used extensively in production designs: +-- +-- Signed/unsigned arithmetic: +-- GHDL normalizes signed/unsigned to std_logic_vector before emitting +-- RTLIL. The write_vhdl output must be valid VHDL-93 with correct +-- std_logic_vector arithmetic. +-- +-- Single-bit sub-range slice (std_logic_vector(N downto N)): +-- GHDL normalizes all array ranges to start_offset=0. write_vhdl +-- must emit std_logic for width-1 wires, not std_logic_vector(0 downto 0). +-- +-- Boolean combined with vector (issue #227 equivalent in VHDL): +-- flag OR'd into vec -- confirms the fix works end-to-end via the +-- GHDL import path. +-- +-- Requires GHDL (--std=93 or --std=08); analysis uses --std=08 for +-- the resize() and signed/unsigned packages. + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity t_vhdl is + port ( + -- Signed arithmetic: GHDL emits $add with sign-extended operands + s_a : in signed(7 downto 0); + s_b : in signed(7 downto 0); + o_sum : out signed(8 downto 0); -- 9-bit sum (guard bit) + + -- Unsigned passthrough + u_a : in unsigned(3 downto 0); + o_u : out std_logic_vector(3 downto 0); + + -- Single-bit sub-range slices: must map to std_logic output ports + bus8 : in std_logic_vector(7 downto 0); + o_b2 : out std_logic; -- bus8(2) + o_b5 : out std_logic; -- bus8(5) + + -- Boolean flag OR'd into every bit of vec (issue #227 via VHDL) + vec : in std_logic_vector(3 downto 0); + flag : in std_logic; + o_flag_vec : out std_logic_vector(3 downto 0) + ); +end entity t_vhdl; + +architecture rtl of t_vhdl is +begin + -- Sign-extending addition + o_sum <= resize(s_a, 9) + resize(s_b, 9); + + -- Unsigned passthrough via std_logic_vector conversion + o_u <= std_logic_vector(u_a); + + -- Single-bit slices + o_b2 <= bus8(2); + o_b5 <= bus8(5); + + -- Boolean + vector + o_flag_vec <= vec or (vec'range => flag); +end architecture rtl; diff --git a/testsuite/write_vhdl/issue227/testsuite.sh b/testsuite/write_vhdl/issue227/testsuite.sh new file mode 100755 index 0000000..6634a11 --- /dev/null +++ b/testsuite/write_vhdl/issue227/testsuite.sh @@ -0,0 +1,213 @@ +#!/bin/sh +# Test: write_vhdl issue #227 -- conditional expression in aggregate +# +# write_vhdl emitted illegal VHDL-93 for boolean/comparison cells with +# output width > 1: +# +# n0 <= (0 => '1' when COND else '0', others => '0'); +# +# VHDL-93 s7.3.2 forbids conditional expressions inside aggregates. +# GHDL rejects this with "')' is expected instead of 'when'". +# +# Five test modules: +# t_exact: exact reporter case ($logic_and y_width=3) +# t_logic: $logic_and/or/not at y_width=4 (broken) and y_width=1 (control) +# t_reduce: $reduce_and/or/xor/xnor at y_width=4 and y_width=1 +# t_compare: $eq/$ne/$lt/$eqx at y_width=4 and y_width=1 +# t_combined: three independent boolean cells feeding one output; +# asserts exactly 3 intermediates (one per cell, not combined) + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +. "$SCRIPT_DIR/../../rename/common.sh" +check_tools + +# --------------------------------------------------------------------------- +# run_test