@@ -549,7 +549,7 @@ module reorder_buffer (
549549 // Partial flush: set tail to flush_tag + 1
550550 // Use age-based arithmetic to handle wrap correctly (extend 5-bit age to 6-bit)
551551 tail_ptr <= head_ptr + { 1'b0 , flush_age} + 1'b1 ;
552- end else if (i_alloc_req.alloc_valid && ! full && ! i_flush_all && ! i_flush_en ) begin
552+ end else if (alloc_en ) begin
553553 // Normal allocation: advance tail
554554 tail_ptr <= tail_ptr + 1'b1 ;
555555 end
@@ -868,7 +868,11 @@ module reorder_buffer (
868868 head_is_mret && ! head_exception &&
869869 ! i_flush_en && ! i_flush_all;
870870
871- // Trap pending signal - asserted when exception at head
871+ // Trap pending signal - asserted when exception at head.
872+ // Note: during the IDLE->TRAP_WAIT transition, both the state check and the
873+ // combinational path assert o_trap_pending simultaneously. This overlap is
874+ // intentional and benign (result is still 1'b1); the state check sustains
875+ // the signal while the combinational term covers the initial detection cycle.
872876 assign o_trap_pending = (serial_state == SERIAL_TRAP_WAIT ) ||
873877 (head_ready && head_exception && ! i_flush_all);
874878 assign o_trap_pc = head_pc;
@@ -914,6 +918,9 @@ module reorder_buffer (
914918 // - Mispredicted taken: redirect to branch_target (actual taken target)
915919 // - Mispredicted not-taken: redirect to pc+4 (fall-through)
916920 if (head_is_mret) begin
921+ // i_mepc is guaranteed stable here: the MRET handshake
922+ // (o_mret_start/i_mret_done) completes before commit_en asserts,
923+ // so the trap unit has finished updating mepc by this point.
917924 o_commit.redirect_pc = i_mepc;
918925 end else if (head_branch_taken) begin
919926 // Mispredicted as not-taken but actually taken -> go to taken target
@@ -960,6 +967,7 @@ module reorder_buffer (
960967 // ===========================================================================
961968
962969`ifndef SYNTHESIS
970+ `ifndef FORMAL
963971 // Check that we don't allocate when full
964972 always @ (posedge i_clk) begin
965973 if (i_rst_n && i_alloc_req.alloc_valid && full) begin
@@ -968,7 +976,7 @@ module reorder_buffer (
968976 end
969977
970978 // Check that dispatch doesn't allocate during flush (invariant: dispatch must be stalled)
971- // Note: alloc_ready doesn't gate on flush, so this assertion documents the required behavior
979+ // Note: alloc_ready also deasserts during flush, but dispatch should be independently stalled
972980 always @ (posedge i_clk) begin
973981 if (i_rst_n && i_alloc_req.alloc_valid && (i_flush_en || i_flush_all)) begin
974982 $error (" Reorder Buffer: Allocation attempted during flush!" );
@@ -995,6 +1003,162 @@ module reorder_buffer (
9951003 $warning (" Reorder Buffer: Serialization state %0d but head not ready" , serial_state);
9961004 end
9971005 end
998- `endif
1006+ `endif // FORMAL
1007+ `endif // SYNTHESIS
1008+
1009+ // ===========================================================================
1010+ // Formal Verification
1011+ // ===========================================================================
1012+
1013+ `ifdef FORMAL
1014+
1015+ initial assume (! i_rst_n);
1016+
1017+ reg f_past_valid;
1018+ initial f_past_valid = 1'b0 ;
1019+ always @ (posedge i_clk) f_past_valid <= 1'b1 ;
1020+
1021+ // Force reset to deassert after the initial cycle and stay deasserted.
1022+ // Without this, the solver can hold i_rst_n low forever, making all
1023+ // i_rst_n-gated asserts vacuously true.
1024+ always @ (posedge i_clk) begin
1025+ if (f_past_valid) assume (i_rst_n);
1026+ end
1027+
1028+ // -------------------------------------------------------------------------
1029+ // Structural constraints (assumes)
1030+ // -------------------------------------------------------------------------
1031+ // These are interface contracts — the upstream dispatch/CDB/branch units
1032+ // guarantee these conditions. They are intentionally kept as assumes
1033+ // (not relaxed) because the ROB's correctness depends on them.
1034+
1035+ // CDB write and branch update cannot target the same tag simultaneously
1036+ always_comb begin
1037+ assume (! (i_cdb_write.valid && i_branch_update.valid &&
1038+ i_cdb_write.tag == i_branch_update.tag));
1039+ end
1040+
1041+ // alloc_valid not asserted during flush (matches existing simulation assertion)
1042+ always_comb begin
1043+ assume (! (i_alloc_req.alloc_valid && (i_flush_en || i_flush_all)));
1044+ end
1045+
1046+ // -------------------------------------------------------------------------
1047+ // Combinational properties (asserts, active when i_rst_n)
1048+ // -------------------------------------------------------------------------
1049+
1050+ always @ (posedge i_clk) begin
1051+ if (i_rst_n) begin
1052+ // full and empty cannot both be true
1053+ p_full_empty_mutex : assert (! (full && empty));
1054+
1055+ // count == tail_ptr - head_ptr
1056+ p_count_consistent : assert (count == (tail_ptr - head_ptr));
1057+
1058+ // full iff pointers match with different MSB
1059+ p_full_matches_ptrs :
1060+ assert (full ==
1061+ ((head_ptr[ReorderBufferTagWidth] != tail_ptr[ReorderBufferTagWidth]) &&
1062+ (head_idx == tail_idx)));
1063+
1064+ // empty iff pointers exactly equal
1065+ p_empty_matches_ptrs : assert (empty == (head_ptr == tail_ptr));
1066+
1067+ // alloc_en implies !full
1068+ p_alloc_not_when_full : assert (! alloc_en || ! full);
1069+
1070+ // commit_en implies head_valid && head_done
1071+ p_commit_requires_valid_done : assert (! commit_en || (head_valid && head_done));
1072+
1073+ // commit output tag equals head_idx
1074+ p_commit_only_at_head : assert (! commit_en || (o_commit.tag == head_idx));
1075+
1076+ // commit_stall implies !commit_en
1077+ p_serial_stall_blocks_commit : assert (! commit_stall || ! commit_en);
1078+ end
1079+ end
1080+
1081+ // -------------------------------------------------------------------------
1082+ // Sequential properties (asserts, require f_past_valid)
1083+ // -------------------------------------------------------------------------
1084+
1085+ always @ (posedge i_clk) begin
1086+ if (f_past_valid && i_rst_n && $past (i_rst_n)) begin
1087+ // After allocation, rob_valid at $past(tail_idx) is set
1088+ if ($past (alloc_en)) begin
1089+ p_alloc_sets_valid : assert (rob_valid[$past (tail_idx)]);
1090+ end
1091+
1092+ // After commit, rob_valid at $past(head_idx) is cleared
1093+ if ($past (commit_en) && ! $past (i_flush_all) && ! $past (i_flush_en)) begin
1094+ p_commit_clears_valid : assert (! rob_valid[$past (head_idx)]);
1095+ end
1096+
1097+ // After flush_all, buffer is empty
1098+ if ($past (i_flush_all)) begin
1099+ p_flush_all_empties : assert (empty);
1100+ end
1101+
1102+ // o_csr_start only in IDLE with CSR at head
1103+ if ($past (o_csr_start)) begin
1104+ p_csr_start_contract : assert ($past (serial_state) == SERIAL_IDLE && $past (head_is_csr));
1105+ end
1106+
1107+ // o_mret_start only in IDLE with MRET at head
1108+ if ($past (o_mret_start)) begin
1109+ p_mret_start_contract : assert ($past (serial_state) == SERIAL_IDLE && $past (head_is_mret));
1110+ end
1111+
1112+ // o_fence_i_flush is registered (one cycle after commit of FENCE.I)
1113+ p_fence_i_flush_delayed :
1114+ assert (o_fence_i_flush == ($past (commit_en) && $past (head_is_fence_i)));
1115+ end
1116+
1117+ // Reset properties (check state after reset deasserts)
1118+ if (f_past_valid && i_rst_n && ! $past (i_rst_n)) begin
1119+ // After reset, all rob_valid bits are 0
1120+ p_reset_clears_valid : assert (rob_valid == '0 );
1121+
1122+ // After reset, head_ptr and tail_ptr are 0
1123+ p_reset_clears_ptrs : assert (head_ptr == '0 && tail_ptr == '0 );
1124+
1125+ // After reset, serial_state is IDLE
1126+ p_reset_serial_idle : assert (serial_state == SERIAL_IDLE );
1127+ end
1128+ end
1129+
1130+ // -------------------------------------------------------------------------
1131+ // Cover properties
1132+ // -------------------------------------------------------------------------
1133+
1134+ always @ (posedge i_clk) begin
1135+ if (i_rst_n) begin
1136+ // Allocation and commit in same cycle
1137+ cover_alloc_and_commit : cover (alloc_en && commit_en);
1138+
1139+ // Buffer reaches full state
1140+ cover_buffer_full : cover (full);
1141+
1142+ // Partial flush occurs
1143+ cover_partial_flush : cover (i_flush_en);
1144+
1145+ // CSR serialization completes
1146+ cover_csr_serialize : cover (serial_state == SERIAL_CSR_EXEC && i_csr_done);
1147+
1148+ // WFI wakes on interrupt
1149+ cover_wfi_wakeup : cover (serial_state == SERIAL_WFI_WAIT && i_interrupt_pending);
1150+
1151+ // MRET completes
1152+ cover_mret_complete : cover (serial_state == SERIAL_MRET_EXEC && i_mret_done);
1153+
1154+ // Exception triggers trap
1155+ cover_exception_trap : cover (serial_state == SERIAL_TRAP_WAIT );
1156+
1157+ // FENCE.I commit generates flush pulse
1158+ cover_fence_i_flush : cover (o_fence_i_flush);
1159+ end
1160+ end
1161+
1162+ `endif // FORMAL
9991163
10001164endmodule : reorder_buffer
0 commit comments