From d0e559a34f8be18af195c8eb15324baf28d9c2a5 Mon Sep 17 00:00:00 2001 From: "N. Engelhardt" Date: Mon, 2 Oct 2023 16:06:26 +0200 Subject: [PATCH 01/32] celledges: support shift ops --- kernel/celledges.cc | 46 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/kernel/celledges.cc b/kernel/celledges.cc index c43ba8db330..0288b62e245 100644 --- a/kernel/celledges.cc +++ b/kernel/celledges.cc @@ -172,6 +172,30 @@ void demux_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) } } +void shift_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) +{ + int width = GetSize(cell->getPort(ID::A)); + int b_width = GetSize(cell->getPort(ID::B)); + + for (int i = 0; i < width; i++){ + for (int k = 0; k < b_width; k++) + db->add_edge(cell, ID::B, k, ID::Y, i, -1); + + if (cell->type.in(ID($shl), ID($sshl))) { + for (int k = i; k >= 0; k--) + db->add_edge(cell, ID::A, k, ID::Y, i, -1); + } + + if (cell->type.in(ID($shr), ID($sshr))) + for (int k = i; k < width; k++) + db->add_edge(cell, ID::A, k, ID::Y, i, -1); + + if (cell->type.in(ID($shift), ID($shiftx))) + for (int k = 0; k < width; k++) + db->add_edge(cell, ID::A, k, ID::Y, i, -1); + } +} + PRIVATE_NAMESPACE_END bool YOSYS_NAMESPACE_PREFIX AbstractCellEdgesDatabase::add_edges_from_cell(RTLIL::Cell *cell) @@ -201,11 +225,10 @@ bool YOSYS_NAMESPACE_PREFIX AbstractCellEdgesDatabase::add_edges_from_cell(RTLIL return true; } - // FIXME: - // if (cell->type.in(ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx))) { - // shift_op(this, cell); - // return true; - // } + if (cell->type.in(ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx))) { + shift_op(this, cell); + return true; + } if (cell->type.in(ID($lt), ID($le), ID($eq), ID($ne), ID($eqx), ID($nex), ID($ge), ID($gt))) { compare_op(this, cell); @@ -227,8 +250,17 @@ bool YOSYS_NAMESPACE_PREFIX AbstractCellEdgesDatabase::add_edges_from_cell(RTLIL return true; } - // FIXME: $mul $div $mod $divfloor $modfloor $slice $concat - // FIXME: $lut $sop $alu $lcu $macc $fa + // FIXME: $mul $div $mod $divfloor $modfloor $pow $slice $concat $bweqx + // FIXME: $lut $sop $alu $lcu $macc $fa $logic_and $logic_or $bwmux + + // FIXME: $_BUF_ $_NOT_ $_AND_ $_NAND_ $_OR_ $_NOR_ $_XOR_ $_XNOR_ $_ANDNOT_ $_ORNOT_ + // FIXME: $_MUX_ $_NMUX_ $_MUX4_ $_MUX8_ $_MUX16_ $_AOI3_ $_OAI3_ $_AOI4_ $_OAI4_ + + // FIXME: $specify2 $specify3 $specrule ??? + // FIXME: $equiv $set_tag $get_tag $overwrite_tag $original_tag + + if (cell->type.in(ID($assert), ID($assume), ID($live), ID($fair), ID($cover), ID($initstate), ID($anyconst), ID($anyseq), ID($allconst), ID($allseq))) + return true; // no-op: these have either no inputs or no outputs return false; } From 2d6d6a8da1dd89a606bff4099e7b84404deb6e07 Mon Sep 17 00:00:00 2001 From: "N. Engelhardt" Date: Mon, 2 Oct 2023 17:29:09 +0200 Subject: [PATCH 02/32] fix handling a_width != y_width --- kernel/celledges.cc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/kernel/celledges.cc b/kernel/celledges.cc index 0288b62e245..fc381f35cdd 100644 --- a/kernel/celledges.cc +++ b/kernel/celledges.cc @@ -174,24 +174,25 @@ void demux_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) void shift_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) { - int width = GetSize(cell->getPort(ID::A)); + int a_width = GetSize(cell->getPort(ID::A)); int b_width = GetSize(cell->getPort(ID::B)); + int y_width = GetSize(cell->getPort(ID::Y)); - for (int i = 0; i < width; i++){ + for (int i = 0; i < y_width; i++){ for (int k = 0; k < b_width; k++) db->add_edge(cell, ID::B, k, ID::Y, i, -1); if (cell->type.in(ID($shl), ID($sshl))) { - for (int k = i; k >= 0; k--) + for (int k = min(i, a_width); k >= 0; k--) db->add_edge(cell, ID::A, k, ID::Y, i, -1); } if (cell->type.in(ID($shr), ID($sshr))) - for (int k = i; k < width; k++) + for (int k = i; k < a_width; k++) db->add_edge(cell, ID::A, k, ID::Y, i, -1); if (cell->type.in(ID($shift), ID($shiftx))) - for (int k = 0; k < width; k++) + for (int k = 0; k < a_width; k++) db->add_edge(cell, ID::A, k, ID::Y, i, -1); } } From 6c562c76bc36ea51af25c26abffe7f92ceae77bd Mon Sep 17 00:00:00 2001 From: "N. Engelhardt" Date: Mon, 2 Oct 2023 18:32:53 +0200 Subject: [PATCH 03/32] fix handling right shifts --- kernel/celledges.cc | 61 +++++++++++++++++++++++++++++++++++++-------- passes/sat/eval.cc | 4 +-- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/kernel/celledges.cc b/kernel/celledges.cc index fc381f35cdd..1e5a68db3a8 100644 --- a/kernel/celledges.cc +++ b/kernel/celledges.cc @@ -174,26 +174,67 @@ void demux_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) void shift_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) { + bool is_signed = cell->getParam(ID::A_SIGNED).as_bool(); int a_width = GetSize(cell->getPort(ID::A)); int b_width = GetSize(cell->getPort(ID::B)); int y_width = GetSize(cell->getPort(ID::Y)); - for (int i = 0; i < y_width; i++){ - for (int k = 0; k < b_width; k++) - db->add_edge(cell, ID::B, k, ID::Y, i, -1); + // how far the maximum value of B is able to shift + int b_range = (1<type.in(ID($shl), ID($sshl))) { - for (int k = min(i, a_width); k >= 0; k--) - db->add_edge(cell, ID::A, k, ID::Y, i, -1); + // << and <<< + b_range_upper = a_width + b_range; + if (is_signed) b_range_upper -= 1; + a_range_lower = max(0, i - b_range); + a_range_upper = min(i+1, a_width); + } else if (cell->type.in(ID($shr), ID($sshr))){ + // >> and >>> + b_range_upper = a_width; + a_range_lower = min(i, a_width - 1); // technically the min is unneccessary as b_range_upper check already skips any i >= a_width, but let's leave the logic in since this is hard enough + a_range_upper = min(i+1 + b_range, a_width); + } else if (cell->type.in(ID($shift), ID($shiftx))) { + // can go both ways depending on sign of B + // 2's complement range is different depending on direction + int b_range_left = (1<<(b_width - 1)); + int b_range_right = (1<<(b_width - 1)) - 1; + b_range_upper = a_width + b_range_left; + a_range_lower = max(0, i - b_range_left); + a_range_upper = min(i+1 + b_range_right, a_width); } - if (cell->type.in(ID($shr), ID($sshr))) - for (int k = i; k < a_width; k++) + if (i < b_range_upper) { + for (int k = a_range_lower; k < a_range_upper; k++) db->add_edge(cell, ID::A, k, ID::Y, i, -1); + } else { + // the only possible influence value is sign extension + if (is_signed) + db->add_edge(cell, ID::A, a_width - 1, ID::Y, i, -1); + } + + for (int k = 0; k < b_width; k++) { + if (cell->type.in(ID($shl), ID($sshl)) && a_width == 1 && is_signed) { + int skip = (1<<(k+1)); + int base = skip -1; + if (i % skip != base) + db->add_edge(cell, ID::B, k, ID::Y, i, -1); + } else if (cell->type.in(ID($shr), ID($sshr)) && is_signed) { + int skip = (1<<(k+1)); + int base = 0; + if (i % skip != base || i < a_width - 1) + db->add_edge(cell, ID::B, k, ID::Y, i, -1); + } else { + db->add_edge(cell, ID::B, k, ID::Y, i, -1); + } + } - if (cell->type.in(ID($shift), ID($shiftx))) - for (int k = 0; k < a_width; k++) - db->add_edge(cell, ID::A, k, ID::Y, i, -1); } } diff --git a/passes/sat/eval.cc b/passes/sat/eval.cc index 05879426e63..acebea6c568 100644 --- a/passes/sat/eval.cc +++ b/passes/sat/eval.cc @@ -543,13 +543,13 @@ struct EvalPass : public Pass { int pos = 0; for (auto &c : tabsigs.chunks()) { - tab_line.push_back(log_signal(RTLIL::SigSpec(tabvals).extract(pos, c.width))); + tab_line.push_back(log_signal(RTLIL::SigSpec(tabvals).extract(pos, c.width), false)); pos += c.width; } pos = 0; for (auto &c : signal.chunks()) { - tab_line.push_back(log_signal(value.extract(pos, c.width))); + tab_line.push_back(log_signal(value.extract(pos, c.width), false)); pos += c.width; } From bdd74e61aed2297cdd3af76da8f43432e6a0afa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Mon, 16 Oct 2023 13:29:47 +0200 Subject: [PATCH 04/32] celledges: Account for shift down of x-bits wrt B port --- kernel/celledges.cc | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/kernel/celledges.cc b/kernel/celledges.cc index 1e5a68db3a8..c6615968f26 100644 --- a/kernel/celledges.cc +++ b/kernel/celledges.cc @@ -226,12 +226,16 @@ void shift_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) if (i % skip != base) db->add_edge(cell, ID::B, k, ID::Y, i, -1); } else if (cell->type.in(ID($shr), ID($sshr)) && is_signed) { - int skip = (1<<(k+1)); - int base = 0; - if (i % skip != base || i < a_width - 1) + bool shift_in_bulk = i < a_width - 1; + // can we jump into the ambient x-bits by toggling B[k]? + bool x_jump = (((y_width - i) & ((1 << (k + 1)) - 1)) != 0 \ + && (((y_width - i) & ~(1 << k)) < (1 << b_width))); + + if (shift_in_bulk || (cell->type == ID($shr) && x_jump)) db->add_edge(cell, ID::B, k, ID::Y, i, -1); } else { - db->add_edge(cell, ID::B, k, ID::Y, i, -1); + if (i < a_width) + db->add_edge(cell, ID::B, k, ID::Y, i, -1); } } From 4cce4916397bcc6ce9e44692f1b31f4edc9369be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Sun, 10 Dec 2023 00:27:42 +0100 Subject: [PATCH 05/32] celledges: s/x_jump/zpad_jump/ --- kernel/celledges.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kernel/celledges.cc b/kernel/celledges.cc index c6615968f26..2d8177d31ae 100644 --- a/kernel/celledges.cc +++ b/kernel/celledges.cc @@ -227,11 +227,11 @@ void shift_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) db->add_edge(cell, ID::B, k, ID::Y, i, -1); } else if (cell->type.in(ID($shr), ID($sshr)) && is_signed) { bool shift_in_bulk = i < a_width - 1; - // can we jump into the ambient x-bits by toggling B[k]? - bool x_jump = (((y_width - i) & ((1 << (k + 1)) - 1)) != 0 \ + // can we jump into the zero-padding by toggling B[k]? + bool zpad_jump = (((y_width - i) & ((1 << (k + 1)) - 1)) != 0 \ && (((y_width - i) & ~(1 << k)) < (1 << b_width))); - if (shift_in_bulk || (cell->type == ID($shr) && x_jump)) + if (shift_in_bulk || (cell->type == ID($shr) && zpad_jump)) db->add_edge(cell, ID::B, k, ID::Y, i, -1); } else { if (i < a_width) From a96c257b3f4860534d7db2b1611c352eceb86161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Mon, 11 Dec 2023 21:36:00 +0100 Subject: [PATCH 06/32] celledges: Add messy rules that do pass the tests This passes `test_cell -edges` on all the types of shift cells. --- kernel/celledges.cc | 55 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/kernel/celledges.cc b/kernel/celledges.cc index 2d8177d31ae..a78c1ab8333 100644 --- a/kernel/celledges.cc +++ b/kernel/celledges.cc @@ -175,9 +175,16 @@ void demux_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) void shift_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) { bool is_signed = cell->getParam(ID::A_SIGNED).as_bool(); + bool is_b_signed = cell->getParam(ID::B_SIGNED).as_bool(); int a_width = GetSize(cell->getPort(ID::A)); int b_width = GetSize(cell->getPort(ID::B)); int y_width = GetSize(cell->getPort(ID::Y)); + int effective_a_width = a_width; + + if (cell->type.in(ID($shift), ID($shiftx)) && is_signed) { + effective_a_width = std::max(y_width, a_width); + //is_signed = false; + } // how far the maximum value of B is able to shift int b_range = (1<type.in(ID($shr), ID($sshr))){ + } else if (cell->type.in(ID($shr), ID($sshr)) || (cell->type.in(ID($shift), ID($shiftx)) && !is_b_signed)){ // >> and >>> b_range_upper = a_width; a_range_lower = min(i, a_width - 1); // technically the min is unneccessary as b_range_upper check already skips any i >= a_width, but let's leave the logic in since this is hard enough a_range_upper = min(i+1 + b_range, a_width); - } else if (cell->type.in(ID($shift), ID($shiftx))) { + } else if (cell->type.in(ID($shift), ID($shiftx)) && is_b_signed) { // can go both ways depending on sign of B // 2's complement range is different depending on direction int b_range_left = (1<<(b_width - 1)); int b_range_right = (1<<(b_width - 1)) - 1; - b_range_upper = a_width + b_range_left; + b_range_upper = effective_a_width + b_range_left; a_range_lower = max(0, i - b_range_left); + if (is_signed) + a_range_lower = min(a_range_lower, a_width - 1); a_range_upper = min(i+1 + b_range_right, a_width); } @@ -223,18 +232,50 @@ void shift_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) if (cell->type.in(ID($shl), ID($sshl)) && a_width == 1 && is_signed) { int skip = (1<<(k+1)); int base = skip -1; - if (i % skip != base) + if (i % skip != base && i - a_width + 2 < 1 << b_width) db->add_edge(cell, ID::B, k, ID::Y, i, -1); - } else if (cell->type.in(ID($shr), ID($sshr)) && is_signed) { + } else if (true && cell->type.in(ID($shift), ID($shiftx)) && a_width == 1 && is_signed && is_b_signed) { + if (k != b_width - 1) { + // can we jump into the zero-padding by toggling B[k]? + bool zpad_jump = (((y_width - i) & ((1 << (k + 1)) - 1)) != 0 \ + && (((y_width - i) & ~(1 << k)) < (1 << (b_width - 1)))); + if (((~(i - 1) & ((1 << (k + 1)) - 1)) != 0 && i < 1 << (b_width - 1)) || zpad_jump) + db->add_edge(cell, ID::B, k, ID::Y, i, -1); + } else { + if ((y_width - 1 - i < (1 << (b_width - 1)) - 1) || (i < (1 << (b_width - 1)))) + db->add_edge(cell, ID::B, k, ID::Y, i, -1); + } + } else if ((cell->type.in(ID($shr), ID($sshr)) || (cell->type.in(ID($shift), ID($shiftx)) && !is_b_signed)) && is_signed) { bool shift_in_bulk = i < a_width - 1; // can we jump into the zero-padding by toggling B[k]? bool zpad_jump = (((y_width - i) & ((1 << (k + 1)) - 1)) != 0 \ && (((y_width - i) & ~(1 << k)) < (1 << b_width))); - if (shift_in_bulk || (cell->type == ID($shr) && zpad_jump)) + if (shift_in_bulk || (cell->type.in(ID($shr), ID($shift), ID($shiftx)) && zpad_jump)) + db->add_edge(cell, ID::B, k, ID::Y, i, -1); + } else if (cell->type.in(ID($sshl), ID($shl)) && is_signed) { + if (i - a_width + 2 < 1 << b_width) + db->add_edge(cell, ID::B, k, ID::Y, i, -1); + } else if (cell->type.in(ID($shl), ID($sshl))) { + if (i - a_width + 1 < 1 << b_width) + db->add_edge(cell, ID::B, k, ID::Y, i, -1); + } else if (cell->type.in(ID($shift), ID($shiftx)) && is_b_signed && !is_signed) { + if (i - a_width < (1 << (b_width - 1))) db->add_edge(cell, ID::B, k, ID::Y, i, -1); + } else if (cell->type.in(ID($shift), ID($shiftx)) && is_b_signed && is_signed) { + if (k != b_width - 1) { + bool r_shift_in_bulk = i < a_width - 1; + // can we jump into the zero-padding by toggling B[k]? + bool r_zpad_jump = (((y_width - i) & ((1 << (k + 1)) - 1)) != 0 \ + && (((y_width - i) & ~(1 << k)) < (1 << (b_width - 1)))); + if (r_shift_in_bulk || r_zpad_jump || i - a_width + 2 <= 1 << (b_width - 1)) + db->add_edge(cell, ID::B, k, ID::Y, i, -1); + } else { + if ((i - a_width + 2) <= (1 << (b_width - 1)) || (y_width - i) < (1 << (b_width - 1))) + db->add_edge(cell, ID::B, k, ID::Y, i, -1); + } } else { - if (i < a_width) + if (i < effective_a_width) db->add_edge(cell, ID::B, k, ID::Y, i, -1); } } From 134eb15c7e25816aba6570ed78b21398c539c395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 19 Jan 2024 11:08:31 +0100 Subject: [PATCH 07/32] celledges: Clean up shift rules --- kernel/celledges.cc | 154 +++++++++++++++++++++++++------------------- 1 file changed, 89 insertions(+), 65 deletions(-) diff --git a/kernel/celledges.cc b/kernel/celledges.cc index a78c1ab8333..2ed0d503605 100644 --- a/kernel/celledges.cc +++ b/kernel/celledges.cc @@ -179,107 +179,131 @@ void shift_op(AbstractCellEdgesDatabase *db, RTLIL::Cell *cell) int a_width = GetSize(cell->getPort(ID::A)); int b_width = GetSize(cell->getPort(ID::B)); int y_width = GetSize(cell->getPort(ID::Y)); - int effective_a_width = a_width; - if (cell->type.in(ID($shift), ID($shiftx)) && is_signed) { - effective_a_width = std::max(y_width, a_width); - //is_signed = false; + // Behavior of the different shift cells: + // + // $shl, $sshl -- shifts left by the amount on B port, B always unsigned + // $shr, $sshr -- ditto right + // $shift, $shiftx -- shifts right by the amount on B port, B optionally signed + // + // Sign extension (if A signed): + // + // $shl, $shr, $shift -- only sign-extends up to size of Y, then shifts in zeroes + // $sshl, $sshr -- fully sign-extends + // $shiftx -- no sign extension + // + // Because $shl, $sshl only shift left, and $shl sign-extens up to size of Y, they + // are effectively the same. + + // the cap below makes sure we don't overflow in the arithmetic further down, though + // it makes the edge data invalid once a_width approaches the order of 2**30 + // (that ever happening is considered improbable) + int b_width_capped = min(b_width, 30); + + int b_high, b_low; + if (!is_b_signed) { + b_high = (1 << b_width_capped) - 1; + b_low = 0; + } else { + b_high = (1 << (b_width_capped - 1)) - 1; + b_low = -(1 << (b_width_capped - 1)); } - // how far the maximum value of B is able to shift - int b_range = (1<type.in(ID($shl), ID($sshl))) { - // << and <<< - b_range_upper = a_width + b_range; + b_range_upper = a_width + b_high; if (is_signed) b_range_upper -= 1; - a_range_lower = max(0, i - b_range); + a_range_lower = max(0, i - b_high); a_range_upper = min(i+1, a_width); - } else if (cell->type.in(ID($shr), ID($sshr)) || (cell->type.in(ID($shift), ID($shiftx)) && !is_b_signed)){ - // >> and >>> + } else if (cell->type.in(ID($shr), ID($sshr)) || (cell->type.in(ID($shift), ID($shiftx)) && !is_b_signed)) { b_range_upper = a_width; - a_range_lower = min(i, a_width - 1); // technically the min is unneccessary as b_range_upper check already skips any i >= a_width, but let's leave the logic in since this is hard enough - a_range_upper = min(i+1 + b_range, a_width); + a_range_lower = min(i, a_width - 1); + a_range_upper = min(i+1 + b_high, a_width); } else if (cell->type.in(ID($shift), ID($shiftx)) && is_b_signed) { // can go both ways depending on sign of B // 2's complement range is different depending on direction - int b_range_left = (1<<(b_width - 1)); - int b_range_right = (1<<(b_width - 1)) - 1; - b_range_upper = effective_a_width + b_range_left; - a_range_lower = max(0, i - b_range_left); + b_range_upper = a_width - b_low; + a_range_lower = max(0, i + b_low); if (is_signed) a_range_lower = min(a_range_lower, a_width - 1); - a_range_upper = min(i+1 + b_range_right, a_width); + a_range_upper = min(i+1 + b_high, a_width); + } else { + log_assert(false && "unreachable"); } if (i < b_range_upper) { for (int k = a_range_lower; k < a_range_upper; k++) db->add_edge(cell, ID::A, k, ID::Y, i, -1); } else { - // the only possible influence value is sign extension + // only influence is through sign extension if (is_signed) db->add_edge(cell, ID::A, a_width - 1, ID::Y, i, -1); } for (int k = 0; k < b_width; k++) { - if (cell->type.in(ID($shl), ID($sshl)) && a_width == 1 && is_signed) { - int skip = (1<<(k+1)); - int base = skip -1; - if (i % skip != base && i - a_width + 2 < 1 << b_width) - db->add_edge(cell, ID::B, k, ID::Y, i, -1); - } else if (true && cell->type.in(ID($shift), ID($shiftx)) && a_width == 1 && is_signed && is_b_signed) { - if (k != b_width - 1) { - // can we jump into the zero-padding by toggling B[k]? - bool zpad_jump = (((y_width - i) & ((1 << (k + 1)) - 1)) != 0 \ - && (((y_width - i) & ~(1 << k)) < (1 << (b_width - 1)))); - if (((~(i - 1) & ((1 << (k + 1)) - 1)) != 0 && i < 1 << (b_width - 1)) || zpad_jump) + // left shifts + if (cell->type.in(ID($shl), ID($sshl))) { + if (a_width == 1 && is_signed) { + int skip = 1 << (k + 1); + int base = skip -1; + if (i % skip != base && i - a_width + 2 < 1 << b_width) + db->add_edge(cell, ID::B, k, ID::Y, i, -1); + } else if (is_signed) { + if (i - a_width + 2 < 1 << b_width) db->add_edge(cell, ID::B, k, ID::Y, i, -1); } else { - if ((y_width - 1 - i < (1 << (b_width - 1)) - 1) || (i < (1 << (b_width - 1)))) + if (i - a_width + 1 < 1 << b_width) db->add_edge(cell, ID::B, k, ID::Y, i, -1); } - } else if ((cell->type.in(ID($shr), ID($sshr)) || (cell->type.in(ID($shift), ID($shiftx)) && !is_b_signed)) && is_signed) { - bool shift_in_bulk = i < a_width - 1; - // can we jump into the zero-padding by toggling B[k]? - bool zpad_jump = (((y_width - i) & ((1 << (k + 1)) - 1)) != 0 \ - && (((y_width - i) & ~(1 << k)) < (1 << b_width))); - - if (shift_in_bulk || (cell->type.in(ID($shr), ID($shift), ID($shiftx)) && zpad_jump)) - db->add_edge(cell, ID::B, k, ID::Y, i, -1); - } else if (cell->type.in(ID($sshl), ID($shl)) && is_signed) { - if (i - a_width + 2 < 1 << b_width) - db->add_edge(cell, ID::B, k, ID::Y, i, -1); - } else if (cell->type.in(ID($shl), ID($sshl))) { - if (i - a_width + 1 < 1 << b_width) - db->add_edge(cell, ID::B, k, ID::Y, i, -1); - } else if (cell->type.in(ID($shift), ID($shiftx)) && is_b_signed && !is_signed) { - if (i - a_width < (1 << (b_width - 1))) - db->add_edge(cell, ID::B, k, ID::Y, i, -1); - } else if (cell->type.in(ID($shift), ID($shiftx)) && is_b_signed && is_signed) { - if (k != b_width - 1) { - bool r_shift_in_bulk = i < a_width - 1; + // right shifts + } else if (cell->type.in(ID($shr), ID($sshr)) || (cell->type.in(ID($shift), ID($shiftx)) && !is_b_signed)) { + if (is_signed) { + bool shift_in_bulk = i < a_width - 1; // can we jump into the zero-padding by toggling B[k]? - bool r_zpad_jump = (((y_width - i) & ((1 << (k + 1)) - 1)) != 0 \ - && (((y_width - i) & ~(1 << k)) < (1 << (b_width - 1)))); - if (r_shift_in_bulk || r_zpad_jump || i - a_width + 2 <= 1 << (b_width - 1)) + bool zpad_jump = (((y_width - i) & ((1 << (k + 1)) - 1)) != 0 \ + && (((y_width - i) & ~(1 << k)) < (1 << b_width))); + + if (shift_in_bulk || (cell->type.in(ID($shr), ID($shift), ID($shiftx)) && zpad_jump)) db->add_edge(cell, ID::B, k, ID::Y, i, -1); } else { - if ((i - a_width + 2) <= (1 << (b_width - 1)) || (y_width - i) < (1 << (b_width - 1))) + if (i < a_width) + db->add_edge(cell, ID::B, k, ID::Y, i, -1); + } + // bidirectional shifts (positive B shifts right, negative left) + } else if (cell->type.in(ID($shift), ID($shiftx)) && is_b_signed) { + if (is_signed) { + if (k != b_width - 1) { + bool r_shift_in_bulk = i < a_width - 1; + // assuming B is positive, can we jump into the upper zero-padding by toggling B[k]? + bool r_zpad_jump = (((y_width - i) & ((1 << (k + 1)) - 1)) != 0 \ + && (((y_width - i) & ~(1 << k)) <= b_high)); + // assuming B is negative, can we influence Y[i] by toggling B[k]? + bool l = a_width - 2 - i >= b_low; + if (a_width == 1) { + // in case of a_width==1 we go into more detailed reasoning + l = l && (~(i - a_width) & ((1 << (k + 1)) - 1)) != 0; + } + if (r_shift_in_bulk || r_zpad_jump || l) + db->add_edge(cell, ID::B, k, ID::Y, i, -1); + } else { + if (y_width - i <= b_high || a_width - 2 - i >= b_low) + db->add_edge(cell, ID::B, k, ID::Y, i, -1); + } + } else { + if (a_width - 1 - i >= b_low) db->add_edge(cell, ID::B, k, ID::Y, i, -1); } } else { - if (i < effective_a_width) - db->add_edge(cell, ID::B, k, ID::Y, i, -1); + log_assert(false && "unreachable"); } } - } } From cb86efa50c69fd8c949c44d50dfbb81056459faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 19 Jan 2024 11:14:10 +0100 Subject: [PATCH 08/32] celledges: Add test of shift cells edge data --- tests/various/celledges_shift.ys | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/various/celledges_shift.ys diff --git a/tests/various/celledges_shift.ys b/tests/various/celledges_shift.ys new file mode 100644 index 00000000000..753c8641e58 --- /dev/null +++ b/tests/various/celledges_shift.ys @@ -0,0 +1 @@ +test_cell -s 1705659041 -n 100 -edges $shift $shiftx $shl $shr $sshl $sshr From d77b792156b439ce7ae6a8900d0166fb1c849ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Thu, 22 Feb 2024 17:22:56 +0100 Subject: [PATCH 09/32] synth: Put in missing bounds check for `-lut` --- techlibs/common/synth.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/techlibs/common/synth.cc b/techlibs/common/synth.cc index e5013678aa4..f604b67c97d 100644 --- a/techlibs/common/synth.cc +++ b/techlibs/common/synth.cc @@ -151,7 +151,7 @@ struct SynthPass : public ScriptPass { flatten = true; continue; } - if (args[argidx] == "-lut") { + if (args[argidx] == "-lut" && argidx + 1 < args.size()) { lut = atoi(args[++argidx].c_str()); continue; } From ba07cba6ce73a979e4ce25a027fbf9cb8723a8c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Thu, 22 Feb 2024 17:23:28 +0100 Subject: [PATCH 10/32] synth: Introduce `-inject` for amending techmap --- techlibs/common/synth.cc | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/techlibs/common/synth.cc b/techlibs/common/synth.cc index f604b67c97d..7da9ba666da 100644 --- a/techlibs/common/synth.cc +++ b/techlibs/common/synth.cc @@ -88,6 +88,10 @@ struct SynthPass : public ScriptPass { log(" read/write collision\" (same result as setting the no_rw_check\n"); log(" attribute on all memories).\n"); log("\n"); + log(" -inject filename\n"); + log(" inject rules from the given file to complement the default\n"); + log(" mapping library in the `techmap` step. this option can be\n"); + log(" repeated.\n"); log("\n"); log("The following commands are executed by this synthesis command:\n"); help_script(); @@ -96,8 +100,8 @@ struct SynthPass : public ScriptPass { string top_module, fsm_opts, memory_opts, abc; bool autotop, flatten, noalumacc, nofsm, noabc, noshare, flowmap, booth; - int lut; + std::vector techmap_inject; void clear_flags() override { @@ -115,6 +119,7 @@ struct SynthPass : public ScriptPass { flowmap = false; booth = false; abc = "abc"; + techmap_inject.clear(); } void execute(std::vector args, RTLIL::Design *design) override @@ -192,6 +197,10 @@ struct SynthPass : public ScriptPass { memory_opts += " -no-rw-check"; continue; } + if (args[argidx] == "-inject" && argidx + 1 < args.size()) { + techmap_inject.push_back(args[++argidx]); + continue; + } break; } extra_args(args, argidx, design); @@ -261,7 +270,17 @@ struct SynthPass : public ScriptPass { run("opt -fast -full"); run("memory_map"); run("opt -full"); - run("techmap"); + if (help_mode) { + run("techmap", " (unless -inject)"); + run("techmap -map +/techmap.v -map ", " (if -inject)"); + } else { + std::string techmap_opts; + if (!techmap_inject.empty()) + techmap_opts += " -map +/techmap.v"; + for (auto fn : techmap_inject) + techmap_opts += stringf(" -map %s", fn.c_str()); + run("techmap" + techmap_opts); + } if (help_mode) { run("techmap -map +/gate2lut.v", "(if -noabc and -lut)"); run("clean; opt_lut", " (if -noabc and -lut)"); From 53ca7b48f86fd3d5bc9ed0f319e24d0086dc12bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Thu, 22 Feb 2024 22:00:56 +0100 Subject: [PATCH 11/32] techmap: Fix help message wording --- passes/techmap/techmap.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/passes/techmap/techmap.cc b/passes/techmap/techmap.cc index 144f596c88c..db395315ce6 100644 --- a/passes/techmap/techmap.cc +++ b/passes/techmap/techmap.cc @@ -1041,8 +1041,8 @@ struct TechmapPass : public Pass { log("\n"); log("When a port on a module in the map file has the 'techmap_autopurge' attribute\n"); log("set, and that port is not connected in the instantiation that is mapped, then\n"); - log("then a cell port connected only to such wires will be omitted in the mapped\n"); - log("version of the circuit.\n"); + log("a cell port connected only to such wires will be omitted in the mapped version\n"); + log("of the circuit.\n"); log("\n"); log("All wires in the modules from the map file matching the pattern _TECHMAP_*\n"); log("or *._TECHMAP_* are special wires that are used to pass instructions from\n"); From d2a7ce04ea5a5bd6d3324e3ec81c3ede0ceab5d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Fri, 1 Mar 2024 10:54:51 +0100 Subject: [PATCH 12/32] synth: Rename `-inject` to `-extra-map` --- techlibs/common/synth.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/techlibs/common/synth.cc b/techlibs/common/synth.cc index 7da9ba666da..68be34f2024 100644 --- a/techlibs/common/synth.cc +++ b/techlibs/common/synth.cc @@ -88,8 +88,8 @@ struct SynthPass : public ScriptPass { log(" read/write collision\" (same result as setting the no_rw_check\n"); log(" attribute on all memories).\n"); log("\n"); - log(" -inject filename\n"); - log(" inject rules from the given file to complement the default\n"); + log(" -extra-map filename\n"); + log(" source extra rules from the given file to complement the default\n"); log(" mapping library in the `techmap` step. this option can be\n"); log(" repeated.\n"); log("\n"); @@ -101,7 +101,7 @@ struct SynthPass : public ScriptPass { string top_module, fsm_opts, memory_opts, abc; bool autotop, flatten, noalumacc, nofsm, noabc, noshare, flowmap, booth; int lut; - std::vector techmap_inject; + std::vector techmap_maps; void clear_flags() override { @@ -119,7 +119,7 @@ struct SynthPass : public ScriptPass { flowmap = false; booth = false; abc = "abc"; - techmap_inject.clear(); + techmap_maps.clear(); } void execute(std::vector args, RTLIL::Design *design) override @@ -197,8 +197,8 @@ struct SynthPass : public ScriptPass { memory_opts += " -no-rw-check"; continue; } - if (args[argidx] == "-inject" && argidx + 1 < args.size()) { - techmap_inject.push_back(args[++argidx]); + if (args[argidx] == "-extra-map" && argidx + 1 < args.size()) { + techmap_maps.push_back(args[++argidx]); continue; } break; @@ -275,9 +275,9 @@ struct SynthPass : public ScriptPass { run("techmap -map +/techmap.v -map ", " (if -inject)"); } else { std::string techmap_opts; - if (!techmap_inject.empty()) + if (!techmap_maps.empty()) techmap_opts += " -map +/techmap.v"; - for (auto fn : techmap_inject) + for (auto fn : techmap_maps) techmap_opts += stringf(" -map %s", fn.c_str()); run("techmap" + techmap_opts); } From 6469d90293b465201c9903f2dc1d02045cc33bcd Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Wed, 7 Feb 2024 18:39:32 +0100 Subject: [PATCH 13/32] write_aiger: Include `$assert` and `$assume` cells in -ywmap output --- backends/aiger/aiger.cc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/backends/aiger/aiger.cc b/backends/aiger/aiger.cc index fe4f7681db3..f2cb5d9bcc7 100644 --- a/backends/aiger/aiger.cc +++ b/backends/aiger/aiger.cc @@ -67,6 +67,8 @@ struct AigerWriter int initstate_ff = 0; dict ywmap_clocks; + vector ywmap_asserts; + vector ywmap_assumes; int mkgate(int a0, int a1) { @@ -269,6 +271,7 @@ struct AigerWriter unused_bits.erase(A); unused_bits.erase(EN); asserts.push_back(make_pair(A, EN)); + ywmap_asserts.push_back(cell); continue; } @@ -279,6 +282,7 @@ struct AigerWriter unused_bits.erase(A); unused_bits.erase(EN); assumes.push_back(make_pair(A, EN)); + ywmap_assumes.push_back(cell); continue; } @@ -852,6 +856,19 @@ struct AigerWriter for (auto &it : init_lines) json.value(it.second); json.end_array(); + + json.name("asserts"); + json.begin_array(); + for (Cell *cell : ywmap_asserts) + json.value(witness_path(cell)); + json.end_array(); + + json.name("assumes"); + json.begin_array(); + for (Cell *cell : ywmap_assumes) + json.value(witness_path(cell)); + json.end_array(); + json.end_object(); } From d8cdc213a64de682952af6f5dc9a12bb2d3f54a4 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Tue, 27 Feb 2024 19:56:47 +0100 Subject: [PATCH 14/32] rename -witness: Bug fix and rename formal cells Rename formal cells in addition to witness signals. This is required to reliably track individual property states for the non-smtbmc flows. Also removes a misplced `break` which resulted in only partial witness renaming. --- backends/smt2/smt2.cc | 13 ++++++++++++- passes/cmds/rename.cc | 15 ++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/backends/smt2/smt2.cc b/backends/smt2/smt2.cc index 5e63e62370d..c702d5e7e54 100644 --- a/backends/smt2/smt2.cc +++ b/backends/smt2/smt2.cc @@ -1117,7 +1117,18 @@ struct Smt2Worker string name_a = get_bool(cell->getPort(ID::A)); string name_en = get_bool(cell->getPort(ID::EN)); - if (cell->name[0] == '$' && cell->attributes.count(ID::src)) + bool private_name = cell->name[0] == '$'; + + if (!private_name && cell->has_attribute(ID::hdlname)) { + for (auto const &part : cell->get_hdlname_attribute()) { + if (part == "_witness_") { + private_name = true; + break; + } + } + } + + if (private_name && cell->attributes.count(ID::src)) decls.push_back(stringf("; yosys-smt2-%s %d %s %s\n", cell->type.c_str() + 1, id, get_id(cell), cell->attributes.at(ID::src).decode_string().c_str())); else decls.push_back(stringf("; yosys-smt2-%s %d %s\n", cell->type.c_str() + 1, id, get_id(cell))); diff --git a/passes/cmds/rename.cc b/passes/cmds/rename.cc index da4ba2f17e9..3f8d807b36d 100644 --- a/passes/cmds/rename.cc +++ b/passes/cmds/rename.cc @@ -134,7 +134,6 @@ static bool rename_witness(RTLIL::Design *design, dict &ca cell->set_hdlname_attribute({ "_witness_", strstr(new_id.c_str(), ".") + 1 }); renames.emplace_back(cell, new_id); } - break; } if (cell->type.in(ID($anyconst), ID($anyseq), ID($anyinit), ID($allconst), ID($allseq))) { @@ -165,6 +164,20 @@ static bool rename_witness(RTLIL::Design *design, dict &ca } } } + + + if (cell->type.in(ID($assert), ID($assume), ID($cover), ID($live), ID($fair), ID($check))) { + has_witness_signals = true; + if (cell->name.isPublic()) + continue; + std::string name = stringf("%s_%s", cell->type.c_str() + 1, cell->name.c_str() + 1); + for (auto &c : name) + if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != '_') + c = '_'; + auto new_id = module->uniquify("\\_witness_." + name); + renames.emplace_back(cell, new_id); + cell->set_hdlname_attribute({ "_witness_", strstr(new_id.c_str(), ".") + 1 }); + } } for (auto rename : renames) { module->rename(rename.first, rename.second); From d03c5e2a00fc1fbb485bdd99bdb264658e316058 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Wed, 21 Feb 2024 16:35:17 +0100 Subject: [PATCH 15/32] smtbmc: Break dependency recursion during unrolling Previously `unroll_stmt` would recurse over the smtlib expressions as well as recursively follow not-yet-emitted definitions the current expression depends on. While the depth of smtlib expressions generated by yosys seems to be reasonably bounded, the dependency chain of not-yet-emitted definitions can grow linearly with the size of the design and linearly in the BMC depth. This makes `unroll_stmt` use a `list` as stack, using python generators and `recursion_helper` function to keep the overall code structure of the previous recursive implementation. --- backends/smt2/smtio.py | 43 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/backends/smt2/smtio.py b/backends/smt2/smtio.py index ebf364f069a..c904aea9531 100644 --- a/backends/smt2/smtio.py +++ b/backends/smt2/smtio.py @@ -79,6 +79,20 @@ def except_hook(exctype, value, traceback): sys.excepthook = except_hook +def recursion_helper(iteration, *request): + stack = [iteration(*request)] + + while stack: + top = stack.pop() + try: + request = next(top) + except StopIteration: + continue + + stack.append(top) + stack.append(iteration(*request)) + + hex_dict = { "0": "0000", "1": "0001", "2": "0010", "3": "0011", "4": "0100", "5": "0101", "6": "0110", "7": "0111", @@ -298,10 +312,22 @@ def replace_in_stmt(self, stmt, pat, repl): return stmt def unroll_stmt(self, stmt): + result = [] + recursion_helper(self._unroll_stmt_into, stmt, result) + return result.pop() + + def _unroll_stmt_into(self, stmt, output, depth=128): if not isinstance(stmt, list): - return stmt + output.append(stmt) + return - stmt = [self.unroll_stmt(s) for s in stmt] + new_stmt = [] + for s in stmt: + if depth: + yield from self._unroll_stmt_into(s, new_stmt, depth - 1) + else: + yield s, new_stmt + stmt = new_stmt if len(stmt) >= 2 and not isinstance(stmt[0], list) and stmt[0] in self.unroll_decls: assert stmt[1] in self.unroll_objs @@ -330,12 +356,19 @@ def unroll_stmt(self, stmt): decl[2] = list() if len(decl) > 0: - decl = self.unroll_stmt(decl) + tmp = [] + if depth: + yield from self._unroll_stmt_into(decl, tmp, depth - 1) + else: + yield decl, tmp + + decl = tmp.pop() self.write(self.unparse(decl), unroll=False) - return self.unroll_cache[key] + output.append(self.unroll_cache[key]) + return - return stmt + output.append(stmt) def p_thread_main(self): while True: From 97db1cb7459af0b07dc8d074b2344c35ef1cc570 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Wed, 21 Feb 2024 16:33:14 +0100 Subject: [PATCH 16/32] smtbmc: Cache hierarchy for loading multiple yw files This will be used by sby/tools/cexenum via the incremental interface. --- backends/smt2/smtbmc.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backends/smt2/smtbmc.py b/backends/smt2/smtbmc.py index 34657be264a..cc47bc3762a 100644 --- a/backends/smt2/smtbmc.py +++ b/backends/smt2/smtbmc.py @@ -649,14 +649,20 @@ def print_msg(msg): num_steps = max(num_steps, step+2) step += 1 +ywfile_hierwitness_cache = None + def ywfile_constraints(inywfile, constr_assumes, map_steps=None, skip_x=False): + global ywfile_hierwitness_cache if map_steps is None: map_steps = {} with open(inywfile, "r") as f: inyw = ReadWitness(f) - inits, seqs, clocks, mems = smt.hierwitness(topmod, allregs=True, blackbox=True) + if ywfile_hierwitness_cache is None: + ywfile_hierwitness_cache = smt.hierwitness(topmod, allregs=True, blackbox=True) + + inits, seqs, clocks, mems = ywfile_hierwitness_cache smt_wires = defaultdict(list) smt_mems = defaultdict(list) From ff6c29ab1e97c28e5d734d85b14901b0b42a02a1 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Mon, 4 Mar 2024 16:52:11 +0100 Subject: [PATCH 17/32] Update abc revision --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e5538815955..6375f116a2a 100644 --- a/Makefile +++ b/Makefile @@ -165,7 +165,7 @@ bumpversion: # is just a symlink to your actual ABC working directory, as 'make mrproper' # will remove the 'abc' directory and you do not want to accidentally # delete your work on ABC.. -ABCREV = 896e5e7 +ABCREV = 0cd90d0 ABCPULL = 1 ABCURL ?= https://github.com/YosysHQ/abc ABCMKARGS = CC="$(CXX)" CXX="$(CXX)" ABC_USE_LIBSTDCXX=1 ABC_USE_NAMESPACE=abc VERBOSE=$(Q) From 1e42b4f0f9cc1d2f4097ea632a93be3473b0c2f7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 5 Mar 2024 00:15:21 +0000 Subject: [PATCH 18/32] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9e9cc711629..2539d5caa9c 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LIBS += -lrt endif endif -YOSYS_VER := 0.38+113 +YOSYS_VER := 0.38+120 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From 0db76c6ec40cea5c8071f7e0fbda88dc21e824a2 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Tue, 5 Mar 2024 14:37:33 +0100 Subject: [PATCH 19/32] tests/sva: Skip sva tests that use SBY until SBY is compatible again This commit is part of a PR that requires corresponding changes in SBY. To prevent CI failures, detect whether those changes already landed and skip the SBY using tests until then. --- tests/sva/runtest.sh | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/tests/sva/runtest.sh b/tests/sva/runtest.sh index 84e79739082..7692a5f9ad9 100644 --- a/tests/sva/runtest.sh +++ b/tests/sva/runtest.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -set -ex +set -e prefix=${1%.ok} prefix=${prefix%.sv} @@ -58,16 +58,33 @@ generate_sby() { } if [ -f $prefix.ys ]; then + set -x $PWD/../../yosys -q -e "Assert .* failed." -s $prefix.ys elif [ -f $prefix.sv ]; then generate_sby pass > ${prefix}_pass.sby generate_sby fail > ${prefix}_fail.sby - sby --yosys $PWD/../../yosys -f ${prefix}_pass.sby - sby --yosys $PWD/../../yosys -f ${prefix}_fail.sby + + # Check that SBY is up to date enough for this yosys version + if sby --help | grep -q -e '--status'; then + set -x + sby --yosys $PWD/../../yosys -f ${prefix}_pass.sby + sby --yosys $PWD/../../yosys -f ${prefix}_fail.sby + else + echo "sva test '${prefix}' requires an up to date SBY, skipping" + fi else generate_sby pass > ${prefix}.sby - sby --yosys $PWD/../../yosys -f ${prefix}.sby + + # Check that SBY is up to date enough for this yosys version + if sby --help | grep -q -e '--status'; then + set -x + sby --yosys $PWD/../../yosys -f ${prefix}.sby + else + echo "sva test '${prefix}' requires an up to date SBY, skipping" + fi fi +{ set +x; } &>/dev/null + touch $prefix.ok From f60b77a7f01ebffe57a5d74e2e7a42e0dbf449e3 Mon Sep 17 00:00:00 2001 From: Catherine Date: Tue, 5 Mar 2024 16:21:12 +0000 Subject: [PATCH 20/32] cxxrtl: add ability to record/replay diagnostics. Note that this functionality is only used by diagnostics emitted by the C++ testbench; diagnostics emitted by the RTL in `eval()` do not need to be recorded since they will be emitted again during replay. --- .../cxxrtl/runtime/cxxrtl/cxxrtl_replay.h | 128 +++++++++++++++--- 1 file changed, 107 insertions(+), 21 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h index 454895a1f27..b8233b007c6 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h @@ -49,11 +49,16 @@ // ::= + // ::= 0x52585843 0x00004c54 // ::= * -// ::= * +// ::= ( | )* // ::= 0xc0000000 ... // ::= 0xc0000001 ... // ::= 0x0??????? + | 0x1??????? + | 0x2??????? | 0x3??????? // , ::= 0x???????? +// ::= | | | +// ::= 0xc0000010 +// ::= 0xc0000011 +// ::= 0xc0000012 +// ::= 0xc0000013 // ::= 0xFFFFFFFF // // The replay log contains sample data, however, it does not cover the entire design. Rather, it only contains sample @@ -61,6 +66,10 @@ // a minimum, and recording speed to a maximum. The player samples any missing data by setting the design state items // to the same values they had during recording, and re-evaluating the design. // +// Packets for diagnostics (prints, breakpoints, assertions, and assumptions) are used solely for diagnostics emitted +// by the C++ testbench driving the simulation, and are not recorded while evaluating the design. (Diagnostics emitted +// by the RTL can be reconstructed at replay time, so recording them would be a waste of space.) +// // Limits // ------ // @@ -109,6 +118,33 @@ namespace cxxrtl { +// A single diagnostic that can be manipulated as an object (including being written to and read from a file). +// This differs from the base CXXRTL interface, where diagnostics can only be emitted via a procedure call, and are +// not materialized as objects. +struct diagnostic { + // The `BREAK` flavor corresponds to a breakpoint, which is a diagnostic type that can currently only be emitted + // by the C++ testbench code. + enum flavor { + BREAK = 0, + PRINT = 1, + ASSERT = 2, + ASSUME = 3, + }; + + flavor type; + std::string message; + std::string location; // same format as the `src` attribute of `$print` or `$check` cell + + diagnostic() + : type(BREAK) {} + + diagnostic(flavor type, const std::string &message, const std::string &location) + : type(type), message(message), location(location) {} + + diagnostic(flavor type, const std::string &message, const char *file, unsigned line) + : type(type), message(message), location(std::string(file) + ':' + std::to_string(line)) {} +}; + // A spool stores CXXRTL design state changes in a file. class spool { public: @@ -127,7 +163,7 @@ class spool { static constexpr uint32_t PACKET_SAMPLE = 0xc0000001; enum sample_flag : uint32_t { - EMPTY = 0, + EMPTY = 0, INCREMENTAL = 1, }; @@ -139,6 +175,9 @@ class spool { static constexpr uint32_t PACKET_CHANGEL = 0x20000000/* | ident */; static constexpr uint32_t PACKET_CHANGEH = 0x30000000/* | ident */; + static constexpr uint32_t PACKET_DIAGNOSTIC = 0xc0000010/* | diagnostic::flavor */; + static constexpr uint32_t DIAGNOSTIC_MASK = 0x0000000f; + static constexpr uint32_t PACKET_END = 0xffffffff; // Writing spools. @@ -281,6 +320,12 @@ class spool { emit_word(data[offset]); } + void write_diagnostic(const diagnostic &diagnostic) { + emit_word(PACKET_DIAGNOSTIC | diagnostic.type); + emit_string(diagnostic.message); + emit_string(diagnostic.location); + } + void write_end() { emit_word(PACKET_END); } @@ -397,11 +442,16 @@ class spool { return true; } - bool read_change_header(uint32_t &header, ident_t &ident) { + bool read_header(uint32_t &header) { header = absorb_word(); - if (header == PACKET_END) - return false; - assert((header & ~(CHANGE_MASK | MAXIMUM_IDENT)) == 0); + return header != PACKET_END; + } + + // This method must be separate from `read_change_data` because `chunks` and `depth` can only be looked up + // if `ident` is known. + bool read_change_ident(uint32_t header, ident_t &ident) { + if ((header & ~(CHANGE_MASK | MAXIMUM_IDENT)) != 0) + return false; // some other packet ident = header & MAXIMUM_IDENT; return true; } @@ -427,6 +477,18 @@ class spool { for (size_t offset = 0; offset < chunks; offset++) data[chunks * index + offset] = absorb_word(); } + + bool read_diagnostic(uint32_t header, diagnostic &diagnostic) { + if ((header & ~DIAGNOSTIC_MASK) != PACKET_DIAGNOSTIC) + return false; // some other packet + uint32_t type = header & DIAGNOSTIC_MASK; + assert(type == diagnostic::BREAK || type == diagnostic::PRINT || + type == diagnostic::ASSERT || type == diagnostic::ASSUME); + diagnostic.type = (diagnostic::flavor)type; + diagnostic.message = absorb_string(); + diagnostic.location = absorb_string(); + return true; + } }; // Opening spools. For certain uses of the record/replay mechanism, two distinct open files (two open files, i.e. @@ -584,6 +646,18 @@ class recorder { return changed; } + void record_diagnostic(const diagnostic &diagnostic) { + assert(streaming); + + // Emit an incremental delta cycle per diagnostic to simplify the logic of the recorder. This is inefficient, but + // diagnostics should be rare enough that this inefficiency does not matter. If it turns out to be an issue, this + // code should be changed to accumulate diagnostics to a buffer that is flushed in `record_{complete,incremental}` + // and also in `advance_time` before the timestamp is changed. (Right now `advance_time` never writes to the spool.) + writer.write_sample(/*incremental=*/true, pointer++, timestamp); + writer.write_diagnostic(diagnostic); + writer.write_end(); + } + void flush() { writer.flush(); } @@ -657,8 +731,9 @@ class player { streaming = true; // Establish the initial state of the design. - initialized = replay(); - assert(initialized); + std::vector diagnostics; + initialized = replay(&diagnostics); + assert(initialized && diagnostics.empty()); } // Returns the pointer of the current sample. @@ -690,8 +765,8 @@ class player { // If this function returns `true`, then `current_pointer() == at_pointer`, and the module contains values that // correspond to this pointer in the replay log. To obtain a valid pointer, call `current_pointer()`; while pointers // are monotonically increasing for each consecutive sample, using arithmetic operations to create a new pointer is - // not allowed. - bool rewind_to(spool::pointer_t at_pointer) { + // not allowed. The `diagnostics` argument, if not `nullptr`, receives the diagnostics recorded in this sample. + bool rewind_to(spool::pointer_t at_pointer, std::vector *diagnostics) { assert(initialized); // The pointers in the replay log start from one that is greater than `at_pointer`. In this case the pointer will @@ -707,9 +782,12 @@ class player { reader.rewind(position_it->second); // Replay samples until eventually arriving to `at_pointer` or encountering end of file. - while(replay()) { + while(replay(diagnostics)) { if (pointer == at_pointer) return true; + + if (diagnostics) + diagnostics->clear(); } return false; } @@ -717,8 +795,8 @@ class player { // If this function returns `true`, then `current_time() <= at_or_before_timestamp`, and the module contains values // that correspond to `current_time()` in the replay log. If `current_time() == at_or_before_timestamp` and there // are several consecutive samples with the same time, the module contains values that correspond to the first of - // these samples. - bool rewind_to_or_before(const time &at_or_before_timestamp) { + // these samples. The `diagnostics` argument, if not `nullptr`, receives the diagnostics recorded in this sample. + bool rewind_to_or_before(const time &at_or_before_timestamp, std::vector *diagnostics) { assert(initialized); // The timestamps in the replay log start from one that is greater than `at_or_before_timestamp`. In this case @@ -734,7 +812,7 @@ class player { reader.rewind(position_it->second); // Replay samples until eventually arriving to or past `at_or_before_timestamp` or encountering end of file. - while (replay()) { + while (replay(diagnostics)) { if (timestamp == at_or_before_timestamp) break; @@ -743,14 +821,17 @@ class player { break; if (next_timestamp > at_or_before_timestamp) break; + + if (diagnostics) + diagnostics->clear(); } return true; } // If this function returns `true`, then `current_pointer()` and `current_time()` are updated for the next sample // and the module now contains values that correspond to that sample. If it returns `false`, there was no next sample - // to read. - bool replay() { + // to read. The `diagnostics` argument, if not `nullptr`, receives the diagnostics recorded in the next sample. + bool replay(std::vector *diagnostics) { assert(streaming); bool incremental; @@ -771,11 +852,16 @@ class player { } uint32_t header; - spool::ident_t ident; - variable var; - while (reader.read_change_header(header, ident)) { - variable &var = variables.at(ident); - reader.read_change_data(header, var.chunks, var.depth, var.curr); + while (reader.read_header(header)) { + spool::ident_t ident; + diagnostic diag; + if (reader.read_change_ident(header, ident)) { + variable &var = variables.at(ident); + reader.read_change_data(header, var.chunks, var.depth, var.curr); + } else if (reader.read_diagnostic(header, diag)) { + if (diagnostics) + diagnostics->push_back(diag); + } else assert(false && "Unrecognized packet header"); } return true; } From e9cd6ca9e8f9c8c729044e83811762ce13e84a12 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 6 Mar 2024 00:16:02 +0000 Subject: [PATCH 21/32] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 35290fbd8c0..81715ded523 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LIBS += -lrt endif endif -YOSYS_VER := 0.38+120 +YOSYS_VER := 0.38+129 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From 570a8f12b58e4cfbd10deb042b217165c2103bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 6 Mar 2024 14:55:43 +0100 Subject: [PATCH 22/32] synth: Fix out-of-sync help message Co-authored-by: N. Engelhardt --- techlibs/common/synth.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/techlibs/common/synth.cc b/techlibs/common/synth.cc index 68be34f2024..74a484d59ae 100644 --- a/techlibs/common/synth.cc +++ b/techlibs/common/synth.cc @@ -271,8 +271,8 @@ struct SynthPass : public ScriptPass { run("memory_map"); run("opt -full"); if (help_mode) { - run("techmap", " (unless -inject)"); - run("techmap -map +/techmap.v -map ", " (if -inject)"); + run("techmap", " (unless -extra-map)"); + run("techmap -map +/techmap.v -map ", " (if -extra-map)"); } else { std::string techmap_opts; if (!techmap_maps.empty()) From b4da6b80f894514979e62230ff8df86b3e38290e Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:56:01 +1300 Subject: [PATCH 23/32] ci: Fix mac builds --- .github/workflows/test-macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-macos.yml b/.github/workflows/test-macos.yml index 9b806a58083..8ca658c3951 100644 --- a/.github/workflows/test-macos.yml +++ b/.github/workflows/test-macos.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Install Dependencies run: | - brew install bison flex gawk libffi pkg-config bash + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install bison flex gawk libffi pkg-config bash - name: Runtime environment shell: bash From 078b876f505647c992479c2afcf50f0850ea6fac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 9 Mar 2024 00:14:37 +0000 Subject: [PATCH 24/32] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 81715ded523..90b4e674a85 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LIBS += -lrt endif endif -YOSYS_VER := 0.38+129 +YOSYS_VER := 0.38+141 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From 5e05300e7b56ef9f4c91e4be6c4906051544b98b Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Mon, 11 Mar 2024 10:55:09 +0100 Subject: [PATCH 25/32] fix compile warning --- kernel/register.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/register.cc b/kernel/register.cc index b5485e06d11..3ccb5ee7848 100644 --- a/kernel/register.cc +++ b/kernel/register.cc @@ -1002,7 +1002,7 @@ struct LicensePass : public Pass { log("This command produces the following notice.\n"); notice(); } - void execute(std::vector args, RTLIL::Design*) override + void execute(std::vector, RTLIL::Design*) override { notice(); } From 42122e240e293f5d01775546448f5f7e2fa0fcd1 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Thu, 7 Mar 2024 13:27:03 +0100 Subject: [PATCH 26/32] smtbmc: Add --track-assumes and --minimize-assumes options The --track-assumes option makes smtbmc keep track of which assumptions were used by the solver when reaching an unsat case and to output that set of assumptions. This is particularly useful to debug PREUNSAT failures. The --minimize-assumes option can be used in addition to --track-assumes which will cause smtbmc to spend additional solving effort to produce a minimal set of assumptions that are sufficient to cause the unsat result. --- backends/smt2/smtbmc.py | 87 +++++++++++++++++++++++--- backends/smt2/smtbmc_incremental.py | 95 +++++++++++++++++++++++++---- backends/smt2/smtio.py | 61 +++++++++++++++++- 3 files changed, 219 insertions(+), 24 deletions(-) diff --git a/backends/smt2/smtbmc.py b/backends/smt2/smtbmc.py index cc47bc3762a..e6b4088dbd7 100644 --- a/backends/smt2/smtbmc.py +++ b/backends/smt2/smtbmc.py @@ -57,6 +57,8 @@ check_witness = False detect_loops = False incremental = None +track_assumes = False +minimize_assumes = False so = SmtOpts() @@ -189,6 +191,15 @@ def help(): --incremental run in incremental mode (experimental) + --track-assumes + track individual assumptions and report a subset of used + assumptions that are sufficient for the reported outcome. This + can be used to debug PREUNSAT failures as well as to find a + smaller set of sufficient assumptions. + + --minimize-assumes + when using --track-assumes, solve for a minimal set of sufficient assumptions. + """ + so.helpmsg()) def usage(): @@ -200,7 +211,8 @@ def usage(): opts, args = getopt.getopt(sys.argv[1:], so.shortopts + "t:higcm:", so.longopts + ["help", "final-only", "assume-skipped=", "smtc=", "cex=", "aig=", "aig-noheader", "yw=", "btorwit=", "presat", "dump-vcd=", "dump-yw=", "dump-vlogtb=", "vlogtb-top=", "dump-smtc=", "dump-all", "noinfo", "append=", - "smtc-init", "smtc-top=", "noinit", "binary", "keep-going", "check-witness", "detect-loops", "incremental"]) + "smtc-init", "smtc-top=", "noinit", "binary", "keep-going", "check-witness", "detect-loops", "incremental", + "track-assumes", "minimize-assumes"]) except: usage() @@ -289,6 +301,10 @@ def usage(): elif o == "--incremental": from smtbmc_incremental import Incremental incremental = Incremental() + elif o == "--track-assumes": + track_assumes = True + elif o == "--minimize-assumes": + minimize_assumes = True elif so.handle(o, a): pass else: @@ -447,6 +463,9 @@ def replace_netref(match): smt = SmtIo(opts=so) +if track_assumes: + smt.smt2_options[':produce-unsat-assumptions'] = 'true' + if noinfo and vcdfile is None and vlogtbfile is None and outconstr is None: smt.produce_models = False @@ -1497,6 +1516,44 @@ def get_active_assert_map(step, active): return assert_map +assume_enables = {} + +def declare_assume_enables(): + def recurse(mod, path, key_base=()): + for expr, desc in smt.modinfo[mod].assumes.items(): + enable = f"|assume_enable {len(assume_enables)}|" + smt.smt2_assumptions[(expr, key_base)] = enable + smt.write(f"(declare-const {enable} Bool)") + assume_enables[(expr, key_base)] = (enable, path, desc) + + for cell, submod in smt.modinfo[mod].cells.items(): + recurse(submod, f"{path}.{cell}", (mod, cell, key_base)) + + recurse(topmod, topmod) + +if track_assumes: + declare_assume_enables() + +def smt_assert_design_assumes(step): + if not track_assumes: + smt_assert_consequent("(|%s_u| s%d)" % (topmod, step)) + return + + if not assume_enables: + return + + def expr_for_assume(assume_key, base=None): + expr, key_base = assume_key + expr_prefix = f"(|{expr}| " + expr_suffix = ")" + while key_base: + mod, cell, key_base = key_base + expr_prefix += f"(|{mod}_h {cell}| " + expr_suffix += ")" + return f"{expr_prefix} s{step}{expr_suffix}" + + for assume_key, (enable, path, desc) in assume_enables.items(): + smt_assert_consequent(f"(=> {enable} {expr_for_assume(assume_key)})") states = list() asserts_antecedent_cache = [list()] @@ -1651,6 +1708,13 @@ def smt_check_sat(expected=["sat", "unsat"]): smt_forall_assert() return smt.check_sat(expected=expected) +def report_tracked_assumptions(msg): + if track_assumes: + print_msg(msg) + for key in smt.get_unsat_assumptions(minimize=minimize_assumes): + enable, path, descr = assume_enables[key] + print_msg(f" In {path}: {descr}") + if incremental: incremental.mainloop() @@ -1664,7 +1728,7 @@ def smt_check_sat(expected=["sat", "unsat"]): break smt_state(step) - smt_assert_consequent("(|%s_u| s%d)" % (topmod, step)) + smt_assert_design_assumes(step) smt_assert_antecedent("(|%s_h| s%d)" % (topmod, step)) smt_assert_antecedent("(not (|%s_is| s%d))" % (topmod, step)) smt_assert_consequent(get_constr_expr(constr_assumes, step)) @@ -1707,6 +1771,7 @@ def smt_check_sat(expected=["sat", "unsat"]): else: print_msg("Temporal induction successful.") + report_tracked_assumptions("Used assumptions:") retstatus = "PASSED" break @@ -1732,7 +1797,7 @@ def smt_check_sat(expected=["sat", "unsat"]): while step < num_steps: smt_state(step) - smt_assert_consequent("(|%s_u| s%d)" % (topmod, step)) + smt_assert_design_assumes(step) smt_assert_antecedent("(|%s_h| s%d)" % (topmod, step)) smt_assert_consequent(get_constr_expr(constr_assumes, step)) @@ -1753,6 +1818,7 @@ def smt_check_sat(expected=["sat", "unsat"]): smt_assert("(distinct (covers_%d s%d) #b%s)" % (coveridx, step, "0" * len(cover_desc))) if smt_check_sat() == "unsat": + report_tracked_assumptions("Used assumptions:") smt_pop() break @@ -1761,13 +1827,14 @@ def smt_check_sat(expected=["sat", "unsat"]): print_msg("Appending additional step %d." % i) smt_state(i) smt_assert_antecedent("(not (|%s_is| s%d))" % (topmod, i)) - smt_assert_consequent("(|%s_u| s%d)" % (topmod, i)) + smt_assert_design_assumes(i) smt_assert_antecedent("(|%s_h| s%d)" % (topmod, i)) smt_assert_antecedent("(|%s_t| s%d s%d)" % (topmod, i-1, i)) smt_assert_consequent(get_constr_expr(constr_assumes, i)) print_msg("Re-solving with appended steps..") if smt_check_sat() == "unsat": print("%s Cannot appended steps without violating assumptions!" % smt.timestamp()) + report_tracked_assumptions("Conflicting assumptions:") found_failed_assert = True retstatus = "FAILED" break @@ -1823,7 +1890,7 @@ def smt_check_sat(expected=["sat", "unsat"]): retstatus = "PASSED" while step < num_steps: smt_state(step) - smt_assert_consequent("(|%s_u| s%d)" % (topmod, step)) + smt_assert_design_assumes(step) smt_assert_antecedent("(|%s_h| s%d)" % (topmod, step)) smt_assert_consequent(get_constr_expr(constr_assumes, step)) @@ -1853,7 +1920,7 @@ def smt_check_sat(expected=["sat", "unsat"]): if step+i < num_steps: smt_state(step+i) smt_assert_antecedent("(not (|%s_is| s%d))" % (topmod, step+i)) - smt_assert_consequent("(|%s_u| s%d)" % (topmod, step+i)) + smt_assert_design_assumes(step + i) smt_assert_antecedent("(|%s_h| s%d)" % (topmod, step+i)) smt_assert_antecedent("(|%s_t| s%d s%d)" % (topmod, step+i-1, step+i)) smt_assert_consequent(get_constr_expr(constr_assumes, step+i)) @@ -1867,7 +1934,8 @@ def smt_check_sat(expected=["sat", "unsat"]): print_msg("Checking assumptions in steps %d to %d.." % (step, last_check_step)) if smt_check_sat() == "unsat": - print("%s Assumptions are unsatisfiable!" % smt.timestamp()) + print_msg("Assumptions are unsatisfiable!") + report_tracked_assumptions("Conficting assumptions:") retstatus = "PREUNSAT" break @@ -1920,13 +1988,14 @@ def smt_check_sat(expected=["sat", "unsat"]): print_msg("Appending additional step %d." % i) smt_state(i) smt_assert_antecedent("(not (|%s_is| s%d))" % (topmod, i)) - smt_assert_consequent("(|%s_u| s%d)" % (topmod, i)) + smt_assert_design_assumes(i) smt_assert_antecedent("(|%s_h| s%d)" % (topmod, i)) smt_assert_antecedent("(|%s_t| s%d s%d)" % (topmod, i-1, i)) smt_assert_consequent(get_constr_expr(constr_assumes, i)) print_msg("Re-solving with appended steps..") if smt_check_sat() == "unsat": - print("%s Cannot append steps without violating assumptions!" % smt.timestamp()) + print_msg("Cannot append steps without violating assumptions!") + report_tracked_assumptions("Conflicting assumptions:") retstatus = "FAILED" break print_anyconsts(step) diff --git a/backends/smt2/smtbmc_incremental.py b/backends/smt2/smtbmc_incremental.py index 1a2a4570312..f43e878f31c 100644 --- a/backends/smt2/smtbmc_incremental.py +++ b/backends/smt2/smtbmc_incremental.py @@ -15,6 +15,14 @@ class InteractiveError(Exception): pass +def mkkey(data): + if isinstance(data, list): + return tuple(map(mkkey, data)) + elif isinstance(data, dict): + raise InteractiveError(f"JSON objects found in assumption key: {data!r}") + return data + + class Incremental: def __init__(self): self.traceidx = 0 @@ -73,17 +81,17 @@ def expr_arg_len(self, expr, min_len, max_len=-1): if min_len is not None and arg_len < min_len: if min_len == max_len: - raise ( + raise InteractiveError( f"{json.dumps(expr[0])} expression must have " f"{min_len} argument{'s' if min_len != 1 else ''}" ) else: - raise ( + raise InteractiveError( f"{json.dumps(expr[0])} expression must have at least " f"{min_len} argument{'s' if min_len != 1 else ''}" ) if max_len is not None and arg_len > max_len: - raise ( + raise InteractiveError( f"{json.dumps(expr[0])} expression can have at most " f"{min_len} argument{'s' if max_len != 1 else ''}" ) @@ -96,14 +104,31 @@ def expr_step(self, expr, smt_out): smt_out.append(f"s{step}") return "module", smtbmc.topmod - def expr_mod_constraint(self, expr, smt_out): - self.expr_arg_len(expr, 1) + def expr_cell(self, expr, smt_out): + self.expr_arg_len(expr, 2) position = len(smt_out) smt_out.append(None) - arg_sort = self.expr(expr[1], smt_out, required_sort=["module", None]) + arg_sort = self.expr(expr[2], smt_out, required_sort=["module", None]) + smt_out.append(")") module = arg_sort[1] + cell = expr[1] + submod = smtbmc.smt.modinfo[module].cells.get(cell) + if submod is None: + raise InteractiveError(f"module {module!r} has no cell {cell!r}") + smt_out[position] = f"(|{module}_h {cell}| " + return ("module", submod) + + def expr_mod_constraint(self, expr, smt_out): suffix = expr[0][3:] - smt_out[position] = f"(|{module}{suffix}| " + self.expr_arg_len(expr, 1, 2 if suffix in ["_a", "_u", "_c"] else 1) + position = len(smt_out) + smt_out.append(None) + arg_sort = self.expr(expr[-1], smt_out, required_sort=["module", None]) + module = arg_sort[1] + if len(expr) == 3: + smt_out[position] = f"(|{module}{suffix} {expr[1]}| " + else: + smt_out[position] = f"(|{module}{suffix}| " smt_out.append(")") return "Bool" @@ -223,20 +248,19 @@ def expr_label(self, expr, smt_out): subexpr = expr[2] if not isinstance(label, str): - raise InteractiveError(f"expression label has to be a string") + raise InteractiveError("expression label has to be a string") smt_out.append("(! ") - smt_out.appedd(label) - smt_out.append(" ") - sort = self.expr(subexpr, smt_out) - + smt_out.append(" :named ") + smt_out.append(label) smt_out.append(")") return sort expr_handlers = { "step": expr_step, + "cell": expr_cell, "mod_h": expr_mod_constraint, "mod_is": expr_mod_constraint, "mod_i": expr_mod_constraint, @@ -302,6 +326,30 @@ def cmd_assert(self, cmd): assert_fn(self.expr_smt(cmd.get("expr"), "Bool")) + def cmd_assert_design_assumes(self, cmd): + step = self.arg_step(cmd) + smtbmc.smt_assert_design_assumes(step) + + def cmd_get_design_assume(self, cmd): + key = mkkey(cmd.get("key")) + return smtbmc.assume_enables.get(key) + + def cmd_update_assumptions(self, cmd): + expr = cmd.get("expr") + key = cmd.get("key") + + + key = mkkey(key) + + result = smtbmc.smt.smt2_assumptions.pop(key, None) + if expr is not None: + expr = self.expr_smt(expr, "Bool") + smtbmc.smt.smt2_assumptions[key] = expr + return result + + def cmd_get_unsat_assumptions(self, cmd): + return smtbmc.smt.get_unsat_assumptions(minimize=bool(cmd.get('minimize'))) + def cmd_push(self, cmd): smtbmc.smt_push() @@ -313,11 +361,14 @@ def cmd_check(self, cmd): def cmd_smtlib(self, cmd): command = cmd.get("command") + response = cmd.get("response", False) if not isinstance(command, str): raise InteractiveError( f"raw SMT-LIB command must be a string, found {json.dumps(command)}" ) smtbmc.smt.write(command) + if response: + return smtbmc.smt.read() def cmd_design_hierwitness(self, cmd=None): allregs = (cmd is None) or bool(cmd.get("allreges", False)) @@ -369,6 +420,21 @@ def cmd_read_yw_trace(self, cmd): return dict(last_step=last_step) + def cmd_modinfo(self, cmd): + fields = cmd.get("fields", []) + + mod = cmd.get("mod") + if mod is None: + mod = smtbmc.topmod + modinfo = smtbmc.smt.modinfo.get(mod) + if modinfo is None: + return None + + result = dict(name=mod) + for field in fields: + result[field] = getattr(modinfo, field, None) + return result + def cmd_ping(self, cmd): return cmd @@ -377,6 +443,10 @@ def cmd_ping(self, cmd): "assert": cmd_assert, "assert_antecedent": cmd_assert, "assert_consequent": cmd_assert, + "assert_design_assumes": cmd_assert_design_assumes, + "get_design_assume": cmd_get_design_assume, + "update_assumptions": cmd_update_assumptions, + "get_unsat_assumptions": cmd_get_unsat_assumptions, "push": cmd_push, "pop": cmd_pop, "check": cmd_check, @@ -384,6 +454,7 @@ def cmd_ping(self, cmd): "design_hierwitness": cmd_design_hierwitness, "write_yw_trace": cmd_write_yw_trace, "read_yw_trace": cmd_read_yw_trace, + "modinfo": cmd_modinfo, "ping": cmd_ping, } diff --git a/backends/smt2/smtio.py b/backends/smt2/smtio.py index c904aea9531..e32f43c60a0 100644 --- a/backends/smt2/smtio.py +++ b/backends/smt2/smtio.py @@ -114,6 +114,7 @@ def __init__(self): self.clocks = dict() self.cells = dict() self.asserts = dict() + self.assumes = dict() self.covers = dict() self.maximize = set() self.minimize = set() @@ -141,6 +142,7 @@ def __init__(self, opts=None): self.recheck = False self.smt2cache = [list()] self.smt2_options = dict() + self.smt2_assumptions = dict() self.p = None self.p_index = solvers_index solvers_index += 1 @@ -602,6 +604,12 @@ def info(self, stmt): else: self.modinfo[self.curmod].covers["%s_c %s" % (self.curmod, fields[2])] = fields[3] + if fields[1] == "yosys-smt2-assume": + if len(fields) > 4: + self.modinfo[self.curmod].assumes["%s_u %s" % (self.curmod, fields[2])] = f'{fields[4]} ({fields[3]})' + else: + self.modinfo[self.curmod].assumes["%s_u %s" % (self.curmod, fields[2])] = fields[3] + if fields[1] == "yosys-smt2-maximize": self.modinfo[self.curmod].maximize.add(fields[2]) @@ -785,8 +793,13 @@ def read(self): return stmt def check_sat(self, expected=["sat", "unsat", "unknown", "timeout", "interrupted"]): + if self.smt2_assumptions: + assume_exprs = " ".join(self.smt2_assumptions.values()) + check_stmt = f"(check-sat-assuming ({assume_exprs}))" + else: + check_stmt = "(check-sat)" if self.debug_print: - print("> (check-sat)") + print(f"> {check_stmt}") if self.debug_file and not self.nocomments: print("; running check-sat..", file=self.debug_file) self.debug_file.flush() @@ -800,7 +813,7 @@ def check_sat(self, expected=["sat", "unsat", "unknown", "timeout", "interrupted for cache_stmt in cache_ctx: self.p_write(cache_stmt + "\n", False) - self.p_write("(check-sat)\n", True) + self.p_write(f"{check_stmt}\n", True) if self.timeinfo: i = 0 @@ -868,7 +881,7 @@ def check_sat(self, expected=["sat", "unsat", "unknown", "timeout", "interrupted if self.debug_file: print("(set-info :status %s)" % result, file=self.debug_file) - print("(check-sat)", file=self.debug_file) + print(check_stmt, file=self.debug_file) self.debug_file.flush() if result not in expected: @@ -945,6 +958,48 @@ def bv2bin(self, v): def bv2int(self, v): return int(self.bv2bin(v), 2) + def get_raw_unsat_assumptions(self): + self.write("(get-unsat-assumptions)") + exprs = set(self.unparse(part) for part in self.parse(self.read())) + unsat_assumptions = [] + for key, value in self.smt2_assumptions.items(): + # normalize expression + value = self.unparse(self.parse(value)) + if value in exprs: + exprs.remove(value) + unsat_assumptions.append(key) + return unsat_assumptions + + def get_unsat_assumptions(self, minimize=False): + if not minimize: + return self.get_raw_unsat_assumptions() + required_assumptions = {} + + while True: + candidate_assumptions = {} + for key in self.get_raw_unsat_assumptions(): + if key not in required_assumptions: + candidate_assumptions[key] = self.smt2_assumptions[key] + + while candidate_assumptions: + + candidate_key, candidate_assume = candidate_assumptions.popitem() + + self.smt2_assumptions = {} + for key, assume in candidate_assumptions.items(): + self.smt2_assumptions[key] = assume + for key, assume in required_assumptions.items(): + self.smt2_assumptions[key] = assume + result = self.check_sat() + + if result == 'unsat': + candidate_assumptions = None + else: + required_assumptions[candidate_key] = candidate_assume + + if candidate_assumptions is not None: + return list(required_assumptions) + def get(self, expr): self.write("(get-value (%s))" % (expr)) return self.parse(self.read())[0][1] From 0944664e60b0c2db98006dcb9e0afde42875cbed Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 00:15:21 +0000 Subject: [PATCH 27/32] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 90b4e674a85..f8b79c6c7ac 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LIBS += -lrt endif endif -YOSYS_VER := 0.38+141 +YOSYS_VER := 0.38+152 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From 00338082b00983e562de8d175329652b46d28c32 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Tue, 12 Mar 2024 08:55:10 +0100 Subject: [PATCH 28/32] Release version 0.39 --- CHANGELOG | 20 +++++++++++++++++++- Makefile | 4 ++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ed91d5e7c0a..119fe35f61b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,8 +2,26 @@ List of major changes and improvements between releases ======================================================= -Yosys 0.38 .. Yosys 0.39-dev +Yosys 0.38 .. Yosys 0.39 -------------------------- + * New commands and options + - Added option "-extra-map" to "synth" pass. + - Added option "-dont_use" to "dfflibmap" pass. + - Added option "-href" to "show" command. + - Added option "-noscopeinfo" to "flatten" pass. + - Added option "-scopename" to "flatten" pass. + + * SystemVerilog + - Added support for packed multidimensional arrays. + + * Various + - Added "$scopeinfo" cells to preserve information about + the hierarchy during flattening. + - Added sequential area output to "stat -liberty". + - Added ability to record/replay diagnostics in cxxrtl backend. + + * Verific support + - Added attributes to module instantiation. Yosys 0.37 .. Yosys 0.38 -------------------------- diff --git a/Makefile b/Makefile index f8b79c6c7ac..00fd26bb1ec 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LIBS += -lrt endif endif -YOSYS_VER := 0.38+152 +YOSYS_VER := 0.39 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo @@ -157,7 +157,7 @@ endif OBJS = kernel/version_$(GIT_REV).o bumpversion: - sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 543faed.. | wc -l`/;" Makefile +# sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 543faed.. | wc -l`/;" Makefile # set 'ABCREV = default' to use abc/ as it is # From 18cec2d9a926c98bfadeffc47d38213139e188a7 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Tue, 12 Mar 2024 08:57:48 +0100 Subject: [PATCH 29/32] Next dev cycle --- CHANGELOG | 3 +++ Makefile | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 119fe35f61b..b172988d5db 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,9 @@ List of major changes and improvements between releases ======================================================= +Yosys 0.39 .. Yosys 0.40-dev +-------------------------- + Yosys 0.38 .. Yosys 0.39 -------------------------- * New commands and options diff --git a/Makefile b/Makefile index 00fd26bb1ec..55941c8c30a 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LIBS += -lrt endif endif -YOSYS_VER := 0.39 +YOSYS_VER := 0.39+0 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo @@ -157,7 +157,7 @@ endif OBJS = kernel/version_$(GIT_REV).o bumpversion: -# sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 543faed.. | wc -l`/;" Makefile + sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 0033808.. | wc -l`/;" Makefile # set 'ABCREV = default' to use abc/ as it is # From b3124f30e42cb0f0f8d8216f39d69e522270833b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 00:15:33 +0000 Subject: [PATCH 30/32] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 55941c8c30a..e55165f46fd 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LIBS += -lrt endif endif -YOSYS_VER := 0.39+0 +YOSYS_VER := 0.39+1 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo From 29e3e10378c32cc5c51ccc65afe5dd0c0d7951aa Mon Sep 17 00:00:00 2001 From: Catherine Date: Wed, 13 Mar 2024 10:04:13 +0000 Subject: [PATCH 31/32] Add WebAssembly as a platform to ISSUE_TEMPLATE/bug_report.yml. --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 27cfd09b7de..66c0b19715a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -34,6 +34,7 @@ body: - macOS - Windows - BSD + - WebAssembly multiple: true validations: required: true From 3231c1cd932b9cffba9651c3264d337093eaed32 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 16 Mar 2024 00:14:56 +0000 Subject: [PATCH 32/32] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e55165f46fd..ff7b141a453 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LIBS += -lrt endif endif -YOSYS_VER := 0.39+1 +YOSYS_VER := 0.39+4 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo