forked from openhwgroup/cv32e40x
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cv32e40x_controller_fsm.sv
1435 lines (1191 loc) · 66.9 KB
/
cv32e40x_controller_fsm.sv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 202[x] Silicon Labs, Inc.
//
// This file, and derivatives thereof are licensed under the
// Solderpad License, Version 2.0 (the "License");
// Use of this file means you agree to the terms and conditions
// of the license and are in full compliance with the License.
// You may obtain a copy of the License at
//
// https://solderpad.org/licenses/SHL-2.0/
//
// Unless required by applicable law or agreed to in writing, software
// and hardware implementations thereof
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESSED OR IMPLIED.
// See the License for the specific language governing permissions and
// limitations under the License.
////////////////////////////////////////////////////////////////////////////////
// Engineer: Øystein Knauserud - oystein.knauserud@silabs.com //
// //
// Additional contributions by: //
// //
// Design Name: cv32e40x_controller_fsm //
// Project Name: CV32E40X //
// Language: SystemVerilog //
// //
// Description: FSM of the pipeline controller //
// //
////////////////////////////////////////////////////////////////////////////////
module cv32e40x_controller_fsm import cv32e40x_pkg::*;
#(
parameter bit X_EXT = 0,
parameter bit SMCLIC = 0,
parameter int SMCLIC_ID_WIDTH = 5
)
(
// Clocks and reset
input logic clk, // Gated clock
input logic rst_n,
input logic fetch_enable_i, // Start executing
// From bypass logic
input ctrl_byp_t ctrl_byp_i,
// From IF stage
input logic [31:0] pc_if_i,
input logic last_op_if_i, // IF stage is handling the last operation of a sequence.
input logic abort_op_if_i, // IF stage contains an operation that will be aborted (bus error or MPU exception)
// From ID stage
input if_id_pipe_t if_id_pipe_i,
input logic alu_jmp_id_i, // Jump in ID
input logic sys_mret_id_i, // mret in ID
input logic alu_en_id_i, // alu_en qualifier for jumps
input logic sys_en_id_i, // sys_en qualifier for mret
input logic first_op_id_i, // ID stage is handling the first operation of a sequence
input logic last_op_id_i, // ID stage is handling the last operation of a sequence
input logic abort_op_id_i, // ID stage contains an (to be) aborted instruction or sequence
// From EX stage
input id_ex_pipe_t id_ex_pipe_i,
input logic branch_decision_ex_i, // branch decision signal from EX ALU
input logic last_op_ex_i, // EX stage contains the last operation of an instruction
// From WB stage
input ex_wb_pipe_t ex_wb_pipe_i,
input logic [1:0] lsu_err_wb_i, // LSU caused bus_error in WB stage, gated with data_rvalid_i inside load_store_unit
input logic last_op_wb_i, // WB stage contains the last operation of an instruction
input logic abort_op_wb_i, // WB stage contains an (to be) aborted instruction or sequence
// From LSU (WB)
input mpu_status_e lsu_mpu_status_wb_i, // MPU status (WB timing)
input logic data_stall_wb_i, // WB stalled by LSU
input logic lsu_valid_wb_i, // LSU instruction in WB is valid
input logic lsu_busy_i, // LSU is busy with outstanding transfers
input logic lsu_interruptible_i, // LSU can be interrupted
input logic lsu_wpt_match_wb_i, // LSU watchpoint trigger (WB)
// Interrupt Controller Signals
input logic irq_wu_ctrl_i, // Irq wakeup control
input logic irq_req_ctrl_i, // Irq request
input logic [9:0] irq_id_ctrl_i, // Irq id
input logic irq_clic_shv_i, // CLIC mode selective hardware vectoring
input logic [7:0] irq_clic_level_i, // CLIC mode current interrupt level
input logic [1:0] irq_clic_priv_i, // CLIC mode current interrupt privilege
// Wakeup signal for WFE (from toplevel input)
input logic wu_wfe_i,
// From cs_registers
input logic [1:0] mtvec_mode_i,
input dcsr_t dcsr_i,
input mcause_t mcause_i,
// Trigger module
input logic etrigger_wb_i, // Trigger module detected match in WB (etrigger)
// Toplevel input
input logic debug_req_i, // External debug request
// All controller FSM outputs
output ctrl_fsm_t ctrl_fsm_o,
// CSR write strobes
input logic csr_wr_in_wb_flush_i,
// Stage valid/ready signals
input logic if_valid_i, // IF stage has valid (non-bubble) data for next stage
input logic id_ready_i, // ID stage is ready for new data
input logic id_valid_i, // ID stage has valid (non-bubble) data for next stage
input logic ex_ready_i, // EX stage is ready for new data
input logic ex_valid_i, // EX stage has valid (non-bubble) data for next stage
input logic wb_ready_i, // WB stage is ready for new data,
input logic wb_valid_i, // WB stage ha valid (non-bubble) data
// Fencei flush handshake
output logic fencei_flush_req_o,
input logic fencei_flush_ack_i,
// Data OBI interface monitor
if_c_obi.monitor m_c_obi_data_if,
// eXtension interface
if_xif.cpu_commit xif_commit_if,
input xif_csr_error_i
);
// FSM state encoding
ctrl_state_e ctrl_fsm_cs, ctrl_fsm_ns;
// Debug state
debug_state_e debug_fsm_cs, debug_fsm_ns;
// Sticky version of lsu_err_wb_i
logic nmi_pending_q;
logic nmi_is_store_q; // 1 for store, 0 for load
// Debug mode
logic debug_mode_n;
logic debug_mode_q;
// Signals used for halting IF after first instruction
// during single step
logic single_step_halt_if_n;
logic single_step_halt_if_q; // Halting IF after issuing one insn in single step mode
// ID signals
logic sys_mret_id; // MRET in ID
logic jmp_id; // JAL, JALR in ID
logic jump_in_id;
logic jump_taken_id;
logic clic_ptr_in_id; // CLIC pointer in ID
logic mret_ptr_in_id; // mret pointer in ID
// EX signals
logic branch_in_ex;
logic branch_taken_ex;
logic branch_taken_n;
logic branch_taken_q;
// WB signals
logic exception_in_wb;
logic [10:0] exception_cause_wb;
logic wfi_in_wb;
logic wfe_in_wb;
logic fencei_in_wb;
logic fence_in_wb;
logic mret_in_wb;
logic mret_ptr_in_wb; // CLIC pointer caused by mret is in WB
logic dret_in_wb;
logic ebreak_in_wb;
logic trigger_match_in_wb; // mcontrol6 trigger in WB
logic etrigger_in_wb; // exception trigger in WB
logic xif_in_wb;
logic clic_ptr_in_wb; // CLIC pointer caused by directly acking an SHV is in WB (no mret)
logic pending_nmi;
logic pending_nmi_early;
logic pending_async_debug;
logic pending_sync_debug;
logic pending_single_step;
logic pending_interrupt;
// Flags for allowing interrupt and debug
logic exception_allowed;
logic interrupt_allowed;
logic nmi_allowed;
logic async_debug_allowed;
logic sync_debug_allowed;
logic single_step_allowed;
// Flag for blocking interrupts due to debug conditions
logic debug_interruptible;
// Flag indicating there is a 'live' CLIC pointer in the pipeline
// Used to block debug and interrupts until pointer fetch is done and the final ISR instruction PC is available.
logic clic_ptr_in_pipeline;
// Internal irq_ack for use when a (clic) pointer reaches ID stage and
// we have single stepping enabled.
logic non_shv_irq_ack;
// Flops for debug cause
logic [2:0] debug_cause_n;
logic [2:0] debug_cause_q;
logic [10:0] exc_cause; // id of taken interrupt. Max width, unused bits are tied off.
logic fence_req_set;
logic fence_req_clr;
logic fence_req_q;
logic fencei_req_and_ack_q;
logic fencei_ongoing;
// Pipeline PC mux control
pipe_pc_mux_e pipe_pc_mux_ctrl;
// Flag for signalling that a new instruction arrived in WB.
// Used for performance counters. High for one cycle, unless WB is halted
// (for fence.i for example), then it will remain high until un-halted.
logic wb_counter_event;
// Gated version of wb_counter_event
// Do not count if halted or killed
logic wb_counter_event_gated;
// Flop for acking flush requests due to CSR writes
logic csr_flush_ack_n;
logic csr_flush_ack_q;
// Flag for checking if multi op instructions are in an interruptible state
logic sequence_in_progress_wb;
logic sequence_interruptible;
// Flag for checking if ID stage can be halted
// Used to not halt sequences in the middle, potentially causing deadlocks
logic sequence_in_progress_id;
logic id_stage_haltable;
// Flag that is high during the cycle after an LSU instruction finishes in WB
logic interrupt_blanking_q;
// Flop for tracking when a pointer fetch is in progress
logic clic_ptr_in_progress_id;
logic clic_ptr_in_progress_id_set;
logic clic_ptr_in_progress_id_clear;
assign sequence_interruptible = !sequence_in_progress_wb;
assign id_stage_haltable = !(sequence_in_progress_id || clic_ptr_in_progress_id);
// Once the fencei handshake is initiated, it must complete and the instruction must retire.
// The instruction retires when fencei_req_and_ack_q = 1
assign fencei_ongoing = fencei_flush_req_o || fencei_req_and_ack_q;
// Mux selector for vectored IRQ PC
// Used for both basic mode and CLIC when shv == 0.
assign ctrl_fsm_o.mtvec_pc_mux = ((mtvec_mode_i == 2'b0) || ((mtvec_mode_i == 2'b11) && !irq_clic_shv_i)) ? 5'h0 : exc_cause[4:0];
// CLIC mode vectored PC mux is always the same as exc_cause.
assign ctrl_fsm_o.mtvt_pc_mux = exc_cause[9:0];
// Mux selector for table jumps
// index for table jumps taken from instruction word in ID stage.
assign ctrl_fsm_o.jvt_pc_mux = if_id_pipe_i.instr.bus_resp.rdata[19:12];
////////////////////////////////////////////////////////////////////
// ID stage
// A jump is taken in ID for jump instructions, and also for mret instructions
// Checking validity of jump/mret instruction with if_id_pipe_i.instr_valid and the respective alu_en/sys_en.
// Using the ID stage local instr_valid would bring halt_id and kill_id into the equation
// causing a path from data_rvalid to instr_addr_o/instr_req_o/instr_memtype_o via pc_set.
assign sys_mret_id = sys_en_id_i && sys_mret_id_i && if_id_pipe_i.instr_valid;
assign jmp_id = alu_en_id_i && alu_jmp_id_i && if_id_pipe_i.instr_valid;
// Detect that a jump is in the ID stage.
// This will also be true for table jumps, as they are encoded as JAL instructions.
// An extra table jump flag is used in the logic for taken jumps to disinguish between
// regular jumps and table jumps.
// Table jumps do an implicit read of the JVT CSR, so csr_stall must be accounted for.
assign jump_in_id = (jmp_id && !if_id_pipe_i.instr_meta.tbljmp && !ctrl_byp_i.jalr_stall) ||
(jmp_id && if_id_pipe_i.instr_meta.tbljmp && !ctrl_byp_i.csr_stall ) ||
(sys_mret_id && !ctrl_byp_i.csr_stall);
// Blocking on branch_taken_q, as a jump has already been taken
assign jump_taken_id = jump_in_id && !branch_taken_q;
// Detect clic pointers in ID
assign clic_ptr_in_id = if_id_pipe_i.instr_valid && if_id_pipe_i.instr_meta.clic_ptr;
// Detect mret pointers in ID
assign mret_ptr_in_id = if_id_pipe_i.instr_valid && if_id_pipe_i.instr_meta.mret_ptr;
// Note: RVFI does not use jump_taken_id (which is not in itself an issue); An assertion in id_stage_sva checks that the jump target remains stable;
// todo: Do we need a similar stability check for branches?
// EX stage
// Branch taken for valid branch instructions in EX with valid decision
assign branch_in_ex = id_ex_pipe_i.alu_bch && id_ex_pipe_i.alu_en && id_ex_pipe_i.instr_valid && branch_decision_ex_i;
// Blocking on branch_taken_q, as a branch ha already been taken
assign branch_taken_ex = branch_in_ex && !branch_taken_q;
// Exception in WB if the following evaluates to 1
// Not checking for ex_wb_pipe_i.last_op to enable exceptions to be taken as soon as possible for
// split load/stores or Zc sequences.
assign exception_in_wb = ((ex_wb_pipe_i.instr.mpu_status != MPU_OK) ||
ex_wb_pipe_i.instr.bus_resp.err ||
ex_wb_pipe_i.illegal_insn ||
(ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_ecall_insn) ||
(ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_ebrk_insn) ||
(lsu_mpu_status_wb_i != MPU_OK)) && ex_wb_pipe_i.instr_valid;
assign ctrl_fsm_o.exception_in_wb = exception_in_wb;
// Set exception cause
assign exception_cause_wb = (ex_wb_pipe_i.instr.mpu_status != MPU_OK) ? EXC_CAUSE_INSTR_FAULT :
ex_wb_pipe_i.instr.bus_resp.err ? EXC_CAUSE_INSTR_BUS_FAULT :
ex_wb_pipe_i.illegal_insn ? EXC_CAUSE_ILLEGAL_INSN :
(ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_ecall_insn) ? EXC_CAUSE_ECALL_MMODE :
(ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_ebrk_insn) ? EXC_CAUSE_BREAKPOINT :
(lsu_mpu_status_wb_i == MPU_WR_FAULT) ? EXC_CAUSE_STORE_FAULT :
EXC_CAUSE_LOAD_FAULT; // (lsu_mpu_status_wb_i == MPU_RE_FAULT)
assign ctrl_fsm_o.exception_cause_wb = exception_cause_wb;
// For now we are always allowed to take exceptions once they arrive in WB.
// For a misaligned load/store with MPU error on the first half, the second half
// will arrive in EX when the first half (with error) arrives in WB. The exception will
// be taken and the bus transaction of the second half will be suppressed by the ctrl_fsm_o.kill_ex signal.
// The only higher priority events are NMI, debug and interrupts, and none of them are allowed if there is
// a load/store in WB.
assign exception_allowed = 1'b1;
// wfi in wb
assign wfi_in_wb = ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_wfi_insn && ex_wb_pipe_i.instr_valid;
// wfe in wb
assign wfe_in_wb = ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_wfe_insn && ex_wb_pipe_i.instr_valid;
// fencei in wb
assign fencei_in_wb = ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_fencei_insn && ex_wb_pipe_i.instr_valid;
// fence in wb
assign fence_in_wb = ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_fence_insn && ex_wb_pipe_i.instr_valid;
// mret in wb - only valid when last_op_wb_i == 1 (which means only mret that did not cause a CLIC pointer fetch)
// Restricts CSR updates due to mret to not happen if the mret caused a CLIC pointer fetch, such CSR updates
// should only happen once the instruction fully completes (pointer arrives in WB).
assign mret_in_wb = ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_mret_insn && ex_wb_pipe_i.instr_valid && last_op_wb_i;
// CLIC pointer (caused by mret) in WB.
assign mret_ptr_in_wb = ex_wb_pipe_i.instr_meta.mret_ptr && ex_wb_pipe_i.instr_valid;
// dret in wb
assign dret_in_wb = ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_dret_insn && ex_wb_pipe_i.instr_valid;
// ebreak in wb
assign ebreak_in_wb = ex_wb_pipe_i.sys_en && ex_wb_pipe_i.sys_ebrk_insn && ex_wb_pipe_i.instr_valid;
// Trigger match in wb
// Trigger_match during debug mode is masked in the trigger logic inside cs_registers.sv
assign trigger_match_in_wb = ((ex_wb_pipe_i.trigger_match || lsu_wpt_match_wb_i) && ex_wb_pipe_i.instr_valid);
// Only set the etrigger_in_wb flag when wb_valid is true (WB is not halted or killed).
// If a higher priority event than taking an exception (NMI, external debug or interrupts) are present, wb_valid_i will be
// suppressed by either halt_wb followed by kill_wb (debug), or kill_wb (NMI/interrupt).
assign etrigger_in_wb = etrigger_wb_i && wb_valid_i;
// An offloaded instruction is in WB
assign xif_in_wb = (ex_wb_pipe_i.xif_en && ex_wb_pipe_i.instr_valid);
// Regular CLIC pointer in WB (not caused by mret)
assign clic_ptr_in_wb = ex_wb_pipe_i.instr_meta.clic_ptr && ex_wb_pipe_i.instr_valid;
// Pending NMI
// Using flopped version to avoid paths from data_err_i/data_rvalid_i to instr_* outputs
assign pending_nmi = nmi_pending_q;
// Early version of the pending_nmi signal, using the unflopped lsu_err_wb_i[0]
// This signal is used for halting the ID stage in the same cycle as the bus error arrives.
// This ensures that any instruction in the ID stage that may depend on the result of the faulted load
// will not propagate to the EX stage. For cycles after lsu_err_wb_i[0] is
// high, ID stage will be halted due to pending_nmi and !nmi_allowed.
assign pending_nmi_early = lsu_err_wb_i[0];
// todo: Halting ID and killing it later will not work for Zce (push/pop)
// dcsr.nmip will always see a pending nmi if nmi_pending_q is set.
// This CSR bit shall not be gated by debug mode or step without stepie
assign ctrl_fsm_o.pending_nmi = nmi_pending_q;
// Debug //
// Single step will need to finish insn in WB, including LSU
// LSU will now set valid_1_o only for second part of misaligned instructions.
// We can always allow single step when checking for wb_valid_i in 'pending_single_step'
// - no other instructions should be in the pipeline.
assign single_step_allowed = 1'b1;
/*
Debug spec 1.0.0 (unratified as of Aug 9th '21)
"If control is transferred to a trap handler while executing the instruction, then Debug Mode is
re-entered immediately after the PC is changed to the trap handler, and the appropriate tval and
cause registers are updated. In this case none of the trap handler is executed, and if the cause was
a pending interrupt no instructions might be executed at all."
Hence, a pending_single_step is asserted if we take an interrupt when we should be stepping.
For any interruptible instructions (non-LSU), at any stage, we would kill the instruction and jump
to debug mode without executing any instructions. Interrupt handler's first instruction will be in dpc.
For LSU instructions that may not be killed (if they reach WB or stay in EX for >1 cycles),
we are not allowed to take interrupts, and we will re-enter debug mode after finishing the LSU.
Interrupt will then be taken when we enter the next step.
*/
assign non_shv_irq_ack = ctrl_fsm_o.irq_ack && !irq_clic_shv_i;
// single step becomes pending when the last operation of an instruction is done in WB (including CLIC pointers), or we ack a non-shv interrupt (including NMI).
// If a CLIC SHV interrupt is taken during single step, a pointer that reaches WB will trigger the debug entry.
// - For un-faulted pointer fetches, the second fetch of the CLIC vectoring took place in ID, and the final SHV handler target address will be available from IF.
// - A faulted pointer fetch does not perform the second fetch. Instead the exception handler fetch will occur before entering debug due to stepping.
// todo: Likely flop the wb_valid/last_op/abort_op to be able to evaluate all debug reasons (debug_req when pointer is in WB is not allowed, while single step is allowed)
// todo: can this be merged with pending_sync_debug in the future?
assign pending_single_step = (!debug_mode_q && dcsr_i.step && ((wb_valid_i && (last_op_wb_i || abort_op_wb_i)) || non_shv_irq_ack || (pending_nmi && nmi_allowed)));
// Detect if there is a live CLIC pointer in the pipeline
// This should block debug and interrupts
generate
if (SMCLIC) begin : gen_clic_pointer_flag
// A CLIC pointer may be in the pipeline from the moment we start fetching (clic_ptr_in_progress_id == 1)
// or while a pointer is in the EX or WB stages.
assign clic_ptr_in_pipeline = (id_ex_pipe_i.instr_valid && id_ex_pipe_i.instr_meta.clic_ptr) ||
(ex_wb_pipe_i.instr_valid && ex_wb_pipe_i.instr_meta.clic_ptr) ||
clic_ptr_in_progress_id;
end else begin : gen_basic_pointer_flag
assign clic_ptr_in_pipeline = 1'b0;
end
endgenerate
// External debug will kill insn in WB, do not allow if LSU is not interruptible, a fence.i handshake is taking place
// or if an offloaded instruction is in WB.
// LSU will not be interruptible if the outstanding counter != 0, or
// a trans_valid has been clocked without ex_valid && wb_ready handshake.
// When a fencei is present in WB and the LSU has completed all tranfers, the fencei handshake will be initiated. This must complete and the fencei instruction must retire before allowing external debug.
// Any multi operation instruction (table jumps, push/pop and double moves) may not be interrupted once the first operation has completed its operation in WB.
// - This is guarded with using the sequence_interruptible, which tracks sequence progress through the WB stage.
// When a CLIC pointer is in the pipeline stages EX or WB, we must block debug.
// - Debug would otherwise kill the pointer and use the address of the pointer for dpc. A following dret would then return to the mtvt table, losing program progress.
//
// Debug entry because of haltreq is disallowed when the LSU is busy and therefore
// haltreq can only cause debug entry on the instruction following a load or store that
// keep the LSU busy. If such load or store however is being single stepped or has an
// associated breakpoint or watchpoint, then debug will be entered because of that
// lower priority reason even though haltreq is asserted. This is okay because if instruction
// timing is considered haltreq should be considered only asserted on the following
// instruction (i.e. the asynchronous haltreq signal is considered asserted too late to
// impact the current instruction in the pipeline).
assign async_debug_allowed = lsu_interruptible_i && !fencei_ongoing && !xif_in_wb && !clic_ptr_in_pipeline && sequence_interruptible;
// synchronous debug entry have far fewer restrictions than asynchronous entries. In principle synchronous debug entry should have the same
// 'allowed' signal as exceptions - that is it should always be possible.
// todo: When XIF is being finished, debug entry vs xif must be reevaluated.
assign sync_debug_allowed = !xif_in_wb;
// Debug pending for any other synchronous reason than single step
assign pending_sync_debug = (trigger_match_in_wb) ||
(ebreak_in_wb && dcsr_i.ebreakm && !debug_mode_q) || // Ebreak with dcsr.ebreakm==1 // todo: add check for WB stage priv level
(ebreak_in_wb && debug_mode_q); // Ebreak during debug_mode restarts execution from dm_halt_addr, as a regular debug entry without CSR updates.
// Debug pending for external debug request, only if not already in debug mode
// Ideally the !debug_mode_q below should be factored into async_debug_allowed, but
// that can currently cause a deadlock if debug_req_i gets asserted while in debug mode, as
// a pending but not allowed async debug will cause the ID stage to halt forever while trying
// to get to an interruptible state.
assign pending_async_debug = debug_req_i && !debug_mode_q;
// Determine cause of debug. Set for all causes of debug entry.
// In case of ebreak during debug mode, the entry code in DEBUG_TAKEN will
// make sure not to update any CSRs.
// The flopped version of this is checked during DEBUG_TAKEN state (one cycle delay)
// todo: update priority according to updated debug spec
// 1: resethaltreq (0x5)
// 2: halt group (0x6)
// 3: haltreq (0x3)
// 4: trigger match (0x2)
// 5: ebreak (0x1)
// 6: single step (0x4)
assign debug_cause_n = (trigger_match_in_wb || etrigger_wb_i) ? DBG_CAUSE_TRIGGER : // Etrigger will enter DEBUG_TAKEN as a single step (no halting), but kill pipeline as non-stepping entries.
(ebreak_in_wb && dcsr_i.ebreakm && !debug_mode_q) ? DBG_CAUSE_EBREAK : // Ebreak during machine mode
(ebreak_in_wb && debug_mode_q) ? DBG_CAUSE_EBREAK : // Ebreak during debug mode
(pending_async_debug && async_debug_allowed) ? DBG_CAUSE_HALTREQ :
(pending_single_step && single_step_allowed) ? DBG_CAUSE_STEP : DBG_CAUSE_NONE;
// Debug cause to CSR from flopped version (valid during DEBUG_TAKEN)
assign ctrl_fsm_o.debug_cause = debug_cause_q;
// interrupt pending comes directly from the interrupt controller
assign pending_interrupt = irq_req_ctrl_i;
// Allow interrupts to be taken only if there is no data request in WB,
// and no trans_valid has been clocked from EX to environment.
// Not allowing interrupts when the core cannot take interrupts due to debug conditions.
// Offloaded instructions in WB also block, as they cannot be killed after commit_kill=0 (EX stage)
// LSU instructions which were suppressed due to previous exceptions or trigger match
// will be interruptable as they were converted to NOP in ID stage.
// When a fencei is present in WB and the LSU has completed all tranfers, the fencei handshake will be initiated. This must complete and the fencei instruction must retire before allowing interrupts.
// TODO:OK:low May allow interuption of Zce to idempotent memories
// Any multi operation instruction (table jumps, push/pop and double moves) may not be interrupted once the first operation has completed its operation in WB.
// - This is guarded with using the sequence_interruptible, which tracks sequence progress through the WB stage.
// When a CLIC pointer is in the pipeline stages EX or WB, we must block interrupts.
// - Interrupt would otherwise kill the pointer and use the address of the pointer for mepc. A following mret would then return to the mtvt table, losing program progress.
assign interrupt_allowed = lsu_interruptible_i && debug_interruptible && !fencei_ongoing && !xif_in_wb && !clic_ptr_in_pipeline && sequence_interruptible && !interrupt_blanking_q;
// Allowing NMI's follow the same rule as regular interrupts, except we don't need to regard blanking of NMIs after a load/store.
assign nmi_allowed = lsu_interruptible_i && debug_interruptible && !fencei_ongoing && !xif_in_wb && !clic_ptr_in_pipeline && sequence_interruptible;
// Do not allow interrupts if in debug mode, or single stepping without dcsr.stepie set.
assign debug_interruptible = !(debug_mode_q || (dcsr_i.step && !dcsr_i.stepie));
// Do not count if we have an exception in WB, trigger match in WB (we do not execute the instruction at trigger address),
// or WB stage is killed or halted.
// When WB is halted, we do not know (yet) if the instruction will retire or get killed.
// Halted WB due to debug will result in WB getting killed
// Halted WB due to fence.i will result in fence.i retire after handshake is done and we count when WB is un-halted
// ctrl_fsm_o.halt_limited_wb will only be set during SLEEP, and only affect the WB stage (not cs_registers)
// In terms of counter events, no event should be counted while either of the WB related halts are asserted.
assign wb_counter_event_gated = wb_counter_event && !exception_in_wb && !trigger_match_in_wb &&
!ctrl_fsm_o.kill_wb && !ctrl_fsm_o.halt_wb && !ctrl_fsm_o.halt_limited_wb;
// Performance counter events
assign ctrl_fsm_o.mhpmevent.minstret = wb_counter_event_gated;
assign ctrl_fsm_o.mhpmevent.compressed = wb_counter_event_gated && ex_wb_pipe_i.instr_meta.compressed;
assign ctrl_fsm_o.mhpmevent.jump = wb_counter_event_gated && ex_wb_pipe_i.alu_jmp_qual;
assign ctrl_fsm_o.mhpmevent.branch = wb_counter_event_gated && ex_wb_pipe_i.alu_bch_qual;
assign ctrl_fsm_o.mhpmevent.branch_taken = wb_counter_event_gated && ex_wb_pipe_i.alu_bch_taken_qual;
assign ctrl_fsm_o.mhpmevent.intr_taken = ctrl_fsm_o.irq_ack;
assign ctrl_fsm_o.mhpmevent.data_read = m_c_obi_data_if.s_req.req && m_c_obi_data_if.s_gnt.gnt && !m_c_obi_data_if.req_payload.we;
assign ctrl_fsm_o.mhpmevent.data_write = m_c_obi_data_if.s_req.req && m_c_obi_data_if.s_gnt.gnt && m_c_obi_data_if.req_payload.we;
assign ctrl_fsm_o.mhpmevent.if_invalid = !if_valid_i && id_ready_i;
assign ctrl_fsm_o.mhpmevent.id_invalid = !id_valid_i && ex_ready_i;
assign ctrl_fsm_o.mhpmevent.ex_invalid = !ex_valid_i && wb_ready_i;
assign ctrl_fsm_o.mhpmevent.wb_invalid = !wb_valid_i;
assign ctrl_fsm_o.mhpmevent.id_jalr_stall = ctrl_byp_i.jalr_stall && !id_valid_i && ex_ready_i;
assign ctrl_fsm_o.mhpmevent.id_ld_stall = ctrl_byp_i.load_stall && !id_valid_i && ex_ready_i;
assign ctrl_fsm_o.mhpmevent.wb_data_stall = data_stall_wb_i;
// Mux used to select PC from the different pipeline stages
always_comb begin
ctrl_fsm_o.pipe_pc = PC_WB;
unique case (pipe_pc_mux_ctrl)
PC_WB: ctrl_fsm_o.pipe_pc = ex_wb_pipe_i.pc;
PC_EX: ctrl_fsm_o.pipe_pc = id_ex_pipe_i.pc;
PC_ID: ctrl_fsm_o.pipe_pc = if_id_pipe_i.pc;
PC_IF: ctrl_fsm_o.pipe_pc = pc_if_i;
default:;
endcase
end
//////////////
// FSM comb //
//////////////
always_comb begin
// Default values
ctrl_fsm_ns = ctrl_fsm_cs;
ctrl_fsm_o.ctrl_busy = 1'b1;
ctrl_fsm_o.instr_req = 1'b1;
ctrl_fsm_o.pc_mux = PC_BOOT;
ctrl_fsm_o.pc_set = 1'b0;
ctrl_fsm_o.irq_ack = 1'b0;
ctrl_fsm_o.irq_id = '0;
ctrl_fsm_o.irq_level = '0;
ctrl_fsm_o.irq_priv = '0;
ctrl_fsm_o.irq_shv = '0;
ctrl_fsm_o.dbg_ack = 1'b0;
// IF stage is halted if an instruction has been issued during single step
// to avoid more than one instructions passing down the pipe.
ctrl_fsm_o.halt_if = single_step_halt_if_q;
// ID stage is halted for regular stalls (i.e. stalls for which the instruction
// currently in ID is not ready to be issued yet). Also halted if interrupt or debug pending
// but not allowed to be taken. This is to create an interruptible bubble in WB.
// Interrupts: Machine mode: Prevent issuing new instructions until we get an interruptible bubble.
// Debug mode: Interrupts are not allowed during debug. Cannot halt ID stage in such a case
// since the dret that brings the core out of debug mode may never get past a halted ID stage.
// Sequences: If we need to halt for debug or interrupt not allowed due to a sequence, we must check if we can
// actually halt the ID stage or not. Halting the same sequence that causes *_allowed to go to 0
// may cause a deadlock.
ctrl_fsm_o.halt_id = ctrl_byp_i.jalr_stall || ctrl_byp_i.load_stall || ctrl_byp_i.csr_stall || ctrl_byp_i.wfi_wfe_stall || ctrl_byp_i.mnxti_id_stall ||
(((pending_interrupt && !interrupt_allowed) || (pending_nmi && !nmi_allowed) || (pending_nmi_early)) && debug_interruptible && id_stage_haltable) ||
(((pending_async_debug && !async_debug_allowed) ||(pending_sync_debug && !sync_debug_allowed)) && id_stage_haltable);
// Halting EX if minstret_stall occurs. Otherwise we would read the wrong minstret value
// Also halting EX if an offloaded instruction in WB may cause an exception, such that a following offloaded
// instruction can correctly receive commit_kill.
// Halting EX when an instruction in WB may cause an interrupt to become pending.
ctrl_fsm_o.halt_ex = ctrl_byp_i.minstret_stall || ctrl_byp_i.xif_exception_stall || ctrl_byp_i.irq_enable_stall || ctrl_byp_i.mnxti_ex_stall;
ctrl_fsm_o.halt_wb = 1'b0;
ctrl_fsm_o.halt_limited_wb = 1'b0;
// By default no stages are killed
ctrl_fsm_o.kill_if = 1'b0;
ctrl_fsm_o.kill_id = 1'b0;
ctrl_fsm_o.kill_ex = 1'b0;
ctrl_fsm_o.kill_wb = 1'b0;
ctrl_fsm_o.csr_restore_mret = 1'b0;
ctrl_fsm_o.csr_restore_mret_ptr = 1'b0;
ctrl_fsm_o.csr_restore_dret = 1'b0;
ctrl_fsm_o.csr_save_cause = 1'b0;
ctrl_fsm_o.csr_cause = 32'h0;
ctrl_fsm_o.csr_clear_minhv = 1'b0;
pipe_pc_mux_ctrl = PC_WB;
exc_cause = 11'b0;
debug_mode_n = debug_mode_q;
ctrl_fsm_o.debug_csr_save = 1'b0;
ctrl_fsm_o.block_data_addr = 1'b0;
// Single step halting of IF
single_step_halt_if_n = single_step_halt_if_q;
// Ensure jumps and branches are taken only once
branch_taken_n = branch_taken_q;
fence_req_set = 1'b0;
fence_req_clr = 1'b1;
ctrl_fsm_o.pc_set_clicv = 1'b0;
ctrl_fsm_o.pc_set_tbljmp = 1'b0;
csr_flush_ack_n = 1'b0;
clic_ptr_in_progress_id_set = 1'b0;
clic_ptr_in_progress_id_clear = 1'b0;
unique case (ctrl_fsm_cs)
RESET: begin
ctrl_fsm_o.instr_req = 1'b0;
if (fetch_enable_i) begin
ctrl_fsm_ns = BOOT_SET;
end
end
// BOOT_SET state required to prevent (timing) path from
// fetch_enable_i via pc_set to instruction interface outputs
BOOT_SET: begin
ctrl_fsm_o.instr_req = 1'b1;
ctrl_fsm_o.pc_mux = PC_BOOT;
ctrl_fsm_o.pc_set = 1'b1;
ctrl_fsm_ns = FUNCTIONAL;
end
FUNCTIONAL: begin
// NMI
if (pending_nmi && nmi_allowed) begin
ctrl_fsm_o.kill_if = 1'b1;
ctrl_fsm_o.kill_id = 1'b1;
ctrl_fsm_o.kill_ex = 1'b1;
ctrl_fsm_o.kill_wb = 1'b1;
ctrl_fsm_o.pc_set = 1'b1;
ctrl_fsm_o.pc_mux = PC_TRAP_NMI;
ctrl_fsm_o.csr_save_cause = 1'b1;
ctrl_fsm_o.csr_cause.irq = 1'b1;
ctrl_fsm_o.csr_cause.exception_code = nmi_is_store_q ? INT_CAUSE_LSU_STORE_FAULT : INT_CAUSE_LSU_LOAD_FAULT;
// Keep mcause.minhv when taking exceptions and interrupts, only cleared on successful pointer fetches or CSR writes.
ctrl_fsm_o.csr_cause.minhv = mcause_i.minhv;
// Save pc from oldest valid instruction
if (ex_wb_pipe_i.instr_valid) begin
pipe_pc_mux_ctrl = PC_WB;
end else if (id_ex_pipe_i.instr_valid) begin
pipe_pc_mux_ctrl = PC_EX;
end else if (if_id_pipe_i.instr_valid) begin
pipe_pc_mux_ctrl = PC_ID;
end else begin
// IF PC will always be valid as it points to the next
// instruction to be issued from IF to ID.
pipe_pc_mux_ctrl = PC_IF;
end
// External debug entry (async)
end else if (pending_async_debug && async_debug_allowed) begin
// Halt the whole pipeline
// Halting makes sure instructions stay in the pipeline stage without propagating to the next.
// This is needed by the debug entry code in the DEBUG_STAKEN state to pick the correct PC for storing in dpc.
// Note that signals like lsu_wpt_match_wb_i and lsu_mpu_status_wb_i will not be constant while WB is halted as
// these behave as an rvalid. In general LSU instruction shall not be allowed to be halted, unless there is a
// watchpoint address match.
ctrl_fsm_o.halt_if = 1'b1;
ctrl_fsm_o.halt_id = 1'b1;
ctrl_fsm_o.halt_ex = 1'b1;
ctrl_fsm_o.halt_wb = 1'b1;
ctrl_fsm_ns = DEBUG_TAKEN;
// IRQ
end else if (pending_interrupt && interrupt_allowed) begin
ctrl_fsm_o.kill_if = 1'b1;
ctrl_fsm_o.kill_id = 1'b1;
ctrl_fsm_o.kill_ex = 1'b1;
ctrl_fsm_o.kill_wb = 1'b1;
ctrl_fsm_o.pc_set = 1'b1;
exc_cause = {1'b0, irq_id_ctrl_i};
ctrl_fsm_o.irq_ack = 1'b1;
ctrl_fsm_o.irq_id = irq_id_ctrl_i;
ctrl_fsm_o.csr_save_cause = 1'b1;
ctrl_fsm_o.csr_cause.irq = 1'b1;
// Default to keeping mcause.minhv. It will only be set to 1 when taking a CLIC SHV interrupt (or by a CSR write).
// For all other cases it keeps its value.
ctrl_fsm_o.csr_cause.minhv = mcause_i.minhv;
if (SMCLIC) begin
ctrl_fsm_o.csr_cause.exception_code = {1'b0, irq_id_ctrl_i};
ctrl_fsm_o.irq_level = irq_clic_level_i;
ctrl_fsm_o.irq_priv = irq_clic_priv_i;
ctrl_fsm_o.irq_shv = irq_clic_shv_i;
if (irq_clic_shv_i) begin
ctrl_fsm_o.pc_mux = PC_TRAP_CLICV;
clic_ptr_in_progress_id_set = 1'b1;
ctrl_fsm_o.pc_set_clicv = 1'b1;
// When taking an SHV interrupt, always set minhv.
// Mcause.minhv will only be cleared when a successful pointer fetch is done, or when it is cleared by a CSR write.
ctrl_fsm_o.csr_cause.minhv = 1'b1;
end else begin
ctrl_fsm_o.pc_mux = PC_TRAP_IRQ;
end
end else begin
ctrl_fsm_o.pc_mux = PC_TRAP_IRQ;
ctrl_fsm_o.csr_cause.exception_code = {1'b0, irq_id_ctrl_i};
end
// Save pc from oldest valid instruction
if (ex_wb_pipe_i.instr_valid) begin
pipe_pc_mux_ctrl = PC_WB;
end else if (id_ex_pipe_i.instr_valid) begin
pipe_pc_mux_ctrl = PC_EX;
end else if (if_id_pipe_i.instr_valid) begin
pipe_pc_mux_ctrl = PC_ID;
end else begin
// IF PC will always be valid as it points to the next
// instruction to be issued from IF to ID.
pipe_pc_mux_ctrl = PC_IF;
end
// Synchronous debug entry (ebreak, trigger match)
end else if (pending_sync_debug && sync_debug_allowed) begin
// Halt the whole pipeline
// Halting makes sure instructions stay in the pipeline stage without propagating to the next.
// This is needed by the debug entry code in the DEBUG_STAKEN state to pick the correct PC for storing in dpc.
// Note that signals like lsu_wpt_match_wb_i and lsu_mpu_status_wb_i will not be constant while WB is halted as
// these behave as an rvalid. In general LSU instruction shall not be allowed to be halted, unless there is a
// watchpoint address match.
ctrl_fsm_o.halt_if = 1'b1;
ctrl_fsm_o.halt_id = 1'b1;
ctrl_fsm_o.halt_ex = 1'b1;
ctrl_fsm_o.halt_wb = 1'b1;
ctrl_fsm_ns = DEBUG_TAKEN;
end else begin
if (exception_in_wb && exception_allowed) begin
// Kill all stages
ctrl_fsm_o.kill_if = 1'b1;
ctrl_fsm_o.kill_id = 1'b1;
ctrl_fsm_o.kill_ex = 1'b1;
ctrl_fsm_o.kill_wb = 1'b0; // All write enables are suppressed, no need to kill WB.
// Set pc to exception handler
ctrl_fsm_o.pc_set = 1'b1;
ctrl_fsm_o.pc_mux = debug_mode_q ? PC_TRAP_DBE : PC_TRAP_EXC;
// Save CSR from WB
pipe_pc_mux_ctrl = PC_WB;
ctrl_fsm_o.csr_save_cause = !debug_mode_q; // Do not update CSRs if in debug mode
ctrl_fsm_o.csr_cause.exception_code = exception_cause_wb;
// Keep mcause.minhv when taking exceptions and interrupts, only cleared on successful pointer fetches or CSR writes.
ctrl_fsm_o.csr_cause.minhv = mcause_i.minhv;
// Special insn
end else if (wfi_in_wb || wfe_in_wb) begin
// Halt the entire pipeline
// WFI/WFE will stay in WB until we exit sleep mode
ctrl_fsm_o.halt_wb = 1'b1;
ctrl_fsm_o.instr_req = 1'b0;
ctrl_fsm_ns = SLEEP;
end else if (fencei_in_wb || fence_in_wb) begin
// fence.i behavior:
//
// - Can be killed due to interrupts and debug at any time before fencei_flush_req_o is asserted (so even after initial cycle in WB).
// - Initially halt entire pipeline, making sure that possibly following loads/stores do not initiate transactions.
// - Wait until the LSU is ready (i.e. write buffer must also be empty and possible NMIs will have been raised).
// - Once the LSU is ready take the NMI if present or otherwise continue fence.i handling by initiating the fencei_flush_req_o handshake.
// - Once the fencei_flush_req_o handshake is complete flush the entire pipeline (branch to next instruction) and retire the fence.i.
// fence behavior:
//
// - Can be killed due to interrupts and debug at any time (so even after initial cycle in WB).
// - Initially halt entire pipeline, making sure that possibly following loads/stores do not initiate transactions.
// - Wait until the LSU is ready (i.e. write buffer must also be empty and possible NMIs will have been raised).
// - Once the LSU is ready take the NMI if present or otherwise continue fence handling.
// - Flush the entire pipeline (branch to next instruction) and retire the fence.
// Halt the pipeline
ctrl_fsm_o.halt_if = 1'b1;
ctrl_fsm_o.halt_id = 1'b1;
ctrl_fsm_o.halt_ex = 1'b1;
ctrl_fsm_o.halt_wb = 1'b1;
// Set fence_req_q when the LSU is no longer busy
fence_req_set = !lsu_busy_i;
fence_req_clr = 1'b0;
if (fencei_in_wb ? fencei_req_and_ack_q : fence_req_q) begin
// Unhalt wb, kill if,id,ex
ctrl_fsm_o.kill_if = 1'b1;
ctrl_fsm_o.kill_id = 1'b1;
ctrl_fsm_o.kill_ex = 1'b1;
ctrl_fsm_o.halt_wb = 1'b0;
// Jump to PC from oldest valid instruction, excluding WB stage
if (id_ex_pipe_i.instr_valid) begin
pipe_pc_mux_ctrl = PC_EX;
end else if (if_id_pipe_i.instr_valid) begin
pipe_pc_mux_ctrl = PC_ID;
end else begin
pipe_pc_mux_ctrl = PC_IF;
end
ctrl_fsm_o.pc_set = 1'b1;
ctrl_fsm_o.pc_mux = PC_WB_PLUS4;
// Clear fence_req_q
fence_req_set = 1'b0;
fence_req_clr = 1'b1;
end
end else if (dret_in_wb) begin
// dret takes jump from WB stage
// Kill previous stages and jump to pc in dpc
ctrl_fsm_o.kill_if = 1'b1;
ctrl_fsm_o.kill_id = 1'b1;
ctrl_fsm_o.kill_ex = 1'b1;
ctrl_fsm_o.pc_mux = PC_DRET;
ctrl_fsm_o.pc_set = 1'b1;
ctrl_fsm_o.csr_restore_dret = 1'b1;
single_step_halt_if_n = 1'b0;
debug_mode_n = 1'b0;
end else if (csr_wr_in_wb_flush_i) begin
// CSR write in WB requires pipeline flush, halt all stages except WB
// EX could contain a load/store, need to avoid its address phase going onto the bus
ctrl_fsm_o.halt_if = 1'b1;
ctrl_fsm_o.halt_id = 1'b1;
ctrl_fsm_o.halt_ex = 1'b1;
// Set flop input to get ack in the next cycle when the write is done.
csr_flush_ack_n = 1'b1;
end else if (csr_flush_ack_q) begin
// Flush pipeline because of CSR update in the previous cycle
ctrl_fsm_o.kill_if = 1'b1;
ctrl_fsm_o.kill_id = 1'b1;
ctrl_fsm_o.kill_ex = 1'b1;
// Jump to PC from oldest valid instruction, excluding WB stage
if (id_ex_pipe_i.instr_valid) begin
pipe_pc_mux_ctrl = PC_EX;
end else if (if_id_pipe_i.instr_valid) begin
pipe_pc_mux_ctrl = PC_ID;
end else begin
pipe_pc_mux_ctrl = PC_IF;
end
ctrl_fsm_o.pc_set = 1'b1;
ctrl_fsm_o.pc_mux = PC_WB_PLUS4;
end else if (branch_taken_ex) begin
ctrl_fsm_o.kill_if = 1'b1;
ctrl_fsm_o.kill_id = 1'b1;
ctrl_fsm_o.pc_mux = PC_BRANCH;
ctrl_fsm_o.pc_set = 1'b1;
// Set flag to avoid further branches to the same target
// if we are stalled
branch_taken_n = 1'b1;
end else if (jump_taken_id) begin
// Jumps in ID (JAL, JALR, mret)
// kill_if
ctrl_fsm_o.kill_if = 1'b1;
if (sys_mret_id) begin
// If the xcause.minhv bit is set for the previous privilege level (mcause.mpp) when an mret is in ID,
// the mret should restart the CLIC pointer fetch using mepc as a pointer to the pointer instead of jumping to
// the address in mepc. If mpp==PRIV_LVL_U, the (not existing) ucause.uinhv bit is assumed to be 0.
// This is done below by signalling pc_set_clicv along with pc_mux=PC_MRET. This will
// treat the mepc as an address to a CLIC pointer. The minhv flag will only be cleared
// when a pointer reaches the WB stage with no faults from fetching.
// ID stage is halted while it contains an mret and at the same time there are CSR writes (including CLIC pointers) EX or WB, hence it is safe to use mcause here.
if (mcause_i.minhv && (mcause_i.mpp == PRIV_LVL_M)) begin
// mcause.minhv set, exception occured during last pointer fetch (or SW wrote it)
// Do another pointer fetch from the address stored in mepc.
ctrl_fsm_o.pc_set = 1'b1;
ctrl_fsm_o.pc_set_clicv = 1'b1; // Treat mepc as a pointer fetch
ctrl_fsm_o.pc_mux = PC_MRET;
// Set flag to avoid further jumps to the same target
// if we are stalled
branch_taken_n = 1'b1;
// Clear flag for halting IF in case of single stepping
// For the single step to complete, both the mret and the pointer must reach WB.
// If no unhalt is done here, the pointer will never reach WB.
single_step_halt_if_n = 1'b0;
end else begin
// xcause.xinhv not set for the previous privilege level, do regular mret
ctrl_fsm_o.pc_mux = PC_MRET;
ctrl_fsm_o.pc_set = 1'b1;
// Set flag to avoid further jumps to the same target
// if we are stalled
branch_taken_n = 1'b1;
end
end else begin
// For table jumps we have two different jumps
// - First part does a pointer fetch from (jvt + (index<<2))
// - Second part jumps to the fetched pointer
// Regular jumps use the regular jump to the target calculated in the ID stage.
ctrl_fsm_o.pc_mux = if_id_pipe_i.instr_meta.tbljmp && !if_id_pipe_i.last_op ? PC_TBLJUMP :
if_id_pipe_i.instr_meta.tbljmp && if_id_pipe_i.last_op ? PC_POINTER : PC_JUMP;
ctrl_fsm_o.pc_set = 1'b1;
ctrl_fsm_o.pc_set_tbljmp = if_id_pipe_i.instr_meta.tbljmp && !if_id_pipe_i.last_op;
// Set flag to avoid further jumps to the same target
// if we are stalled
branch_taken_n = 1'b1;
end
end else if (clic_ptr_in_id || mret_ptr_in_id) begin
// todo e40s: Factor in integrity related errors
if (!(if_id_pipe_i.instr.bus_resp.err || (if_id_pipe_i.instr.mpu_status != MPU_OK))) begin
if (!branch_taken_q) begin
ctrl_fsm_o.pc_set = 1'b1;
ctrl_fsm_o.pc_mux = PC_POINTER;
ctrl_fsm_o.kill_if = 1'b1;
branch_taken_n = 1'b1;
end
end
end
// CLIC pointer in ID clears pointer fetch flag
if (clic_ptr_in_id && id_valid_i && ex_ready_i) begin
clic_ptr_in_progress_id_clear = 1'b1;
end
// Regular mret in WB restores CSR regs
if (mret_in_wb && !ctrl_fsm_o.kill_wb) begin
ctrl_fsm_o.csr_restore_mret = !debug_mode_q;
end
// For mret that caused a CLIC pointer fetch, CSR updates will happen once the pointer reaches WB.
// If the pointer has associated exceptions, the csr_restore_mret_ptr will not happen