diff --git a/src_Core/CPU/Core.bsv b/src_Core/CPU/Core.bsv index 7f05d80..03631c8 100644 --- a/src_Core/CPU/Core.bsv +++ b/src_Core/CPU/Core.bsv @@ -84,6 +84,8 @@ import Bypass::*; import CsrFile :: *; +import Cur_Cycle :: *; + interface CoreReq; method Action start( Addr startpc, @@ -138,6 +140,19 @@ interface Core; // Bluespec: external interrupt to enter debug mode method Action setDEIP (Bit #(1) v); + +`ifdef INCLUDE_GDB_CONTROL + method Action halt_to_debug_mode_req; + + (* always_ready *) + method Bool is_debug_halted; + + method Action resume_from_debug_mode; + + method Data csr_read (Bit #(12) csr_addr); + method Action csr_write (Bit #(12) csr_addr, Data data); + +`endif endinterface // fixpoint to instantiate modules @@ -158,7 +173,11 @@ module mkCore#(CoreId coreId)(Core); outOfReset <= True; endrule - Reg#(Bool) started <- mkReg(False); + Reg#(Bool) started <- mkReg(False); // only used for deadlock check + +`ifdef INCLUDE_GDB_CONTROL + Reg#(Bool) rg_debug_halted <- mkReg (False); +`endif // front end FetchStage fetchStage <- mkFetchStage; @@ -512,6 +531,21 @@ module mkCore#(CoreId coreId)(Core); endinterface); CommitStage commitStage <- mkCommitStage(commitInput); + (* mutually_exclusive = "coreFix.aluExe_0.doRegReadAlu, commitStage.rl_enter_debug_mode_flush" *) + (* mutually_exclusive = "coreFix.aluExe_1.doRegReadAlu, commitStage.rl_enter_debug_mode_flush" *) + (* mutually_exclusive = "coreFix.aluExe_0.doDispatchAlu, commitStage.rl_enter_debug_mode_flush" *) + (* mutually_exclusive = "coreFix.aluExe_1.doDispatchAlu, commitStage.rl_enter_debug_mode_flush" *) + (* mutually_exclusive = "coreFix.fpuMulDivExe_0.doFinishIntMul, commitStage.rl_enter_debug_mode_flush" *) + (* mutually_exclusive = "coreFix.fpuMulDivExe_0.doFinishIntDiv, commitStage.rl_enter_debug_mode_flush" *) + (* mutually_exclusive = "coreFix.fpuMulDivExe_0.doFinishFpFma, commitStage.rl_enter_debug_mode_flush" *) + (* mutually_exclusive = "coreFix.fpuMulDivExe_0.doFinishFpDiv, commitStage.rl_enter_debug_mode_flush" *) + (* mutually_exclusive = "coreFix.fpuMulDivExe_0.doFinishFpSqrt, commitStage.rl_enter_debug_mode_flush" *) + (* mutually_exclusive = "coreFix.fpuMulDivExe_0.doFinishFpSqrt, commitStage.rl_enter_debug_mode_flush" *) + + rule rl_bogus_dummy (False); + // Just to allow the scheduling attributes above + endrule + // send rob enq time to reservation stations (* fire_when_enabled, no_implicit_conditions *) rule sendRobEnqTime; @@ -903,6 +937,19 @@ module mkCore#(CoreId coreId)(Core); endrule `endif +`ifdef INCLUDE_GDB_CONTROL + // ================================================================ + // Stopping into debug mode + + rule rl_debug_halt_actions ((! rg_debug_halted) && commitStage.is_debug_halted); + $display ("%0d: %m.rl_debug_halt_actions", cur_cycle); + rg_debug_halted <= True; + endrule + +`endif + + // ================================================================ + interface CoreReq coreReq; method Action start( Bit#(64) startpc, @@ -910,6 +957,9 @@ module mkCore#(CoreId coreId)(Core); ); fetchStage.start(startpc); started <= True; +`ifdef INCLUDE_GDB_CONTROL + rg_debug_halted <= False; +`endif mmio.setHtifAddrs(toHostAddr, fromHostAddr); // start rename debug commitStage.startRenameDebug; @@ -980,5 +1030,38 @@ module mkCore#(CoreId coreId)(Core); // Bluespec: external interrupt to enter debug mode method Action setDEIP (v) = csrf.setDEIP (v); -endmodule +`ifdef INCLUDE_GDB_CONTROL + method Action halt_to_debug_mode_req () if (! rg_debug_halted); + $display ("%0d: %m.halt_to_debug_mode_req", cur_cycle); + started <= False; + fetchStage.stop; + commitStage.halt_to_debug_mode_req; + endmethod + + method Bool is_debug_halted; + return rg_debug_halted; + endmethod + + method Action resume_from_debug_mode if (rg_debug_halted); + let startpc = csrf.dpc_read; + fetchStage.resume_from_debug_mode (startpc); + commitStage.resume_from_debug_mode; + started <= True; + rg_debug_halted <= False; + + $display ("%0d: %m.resume_from_debug_mode, dpc = 0x%0h", cur_cycle, startpc); + endmethod + + // TODO_DEBUG: was part of method cond: commitStage.is_debug_halted && + method Data csr_read (Bit #(12) csr_addr) if (rg_debug_halted); + return csrf.rd (unpack (csr_addr)); + endmethod + + // TODO_DEBUG: was part of method cond: commitStage.is_debug_halted && + method Action csr_write (Bit #(12) csr_addr, Data data) if (rg_debug_halted); + csrf.csrInstWr (unpack (csr_addr), data); + endmethod +`endif + +endmodule diff --git a/src_Core/CPU/CsrFile.bsv b/src_Core/CPU/CsrFile.bsv index 479437c..1e43050 100644 --- a/src_Core/CPU/CsrFile.bsv +++ b/src_Core/CPU/CsrFile.bsv @@ -37,6 +37,8 @@ import GetPut::*; import BuildVector::*; //import TRNG::*; +import SoC_Map :: *; + interface CsrFile; // Read method Data rd(CSR csr); @@ -90,6 +92,24 @@ interface CsrFile; // terminate method ActionValue#(void) terminate; + +`ifdef INCLUDE_GDB_CONTROL + // Read dpc + method Addr dpc_read (); + + // Update dpc + method Action dpc_write (Addr pc); + + // Check whether to enter Debug Mode based on dcsr.{ebreakm, ebreaks, ebreaku} + method Bool dcsr_stop_for_break; + + // Check whether to enter Debug Mode based on dcsr.step + method Bool dcsr_stop_for_step; + + // Update 'cause' in DCSR + (* always_ready *) + method Action dcsr_cause_write (Bit #(3) dcsr_cause); +`endif endinterface // Fancy Reg functions @@ -501,6 +521,30 @@ module mkCsrFile #(Data hartid)(CsrFile); StatsCsr stats_module <- mkStatsCsr; Reg#(Data) stats_csr = stats_module.reg_ifc; +`ifdef INCLUDE_GDB_CONTROL + // DCSR is 32b even in RV64 + Bit #(32) dcsr_reset_value = {4'h4, // [31:28] xdebugver + 12'h0, // [27:16] reserved + 1'h0, // [15] ebreakm + 1'h0, // [14] reserved + 1'h0, // [13] ebreaks + 1'h0, // [12] ebreaku + 1'h0, // [11] stepie + 1'h0, // [10] stopcount + 1'h0, // [9] stoptime + 3'h0, // [8:7] cause // WARNING: 0 is non-standard + 1'h0, // [5] reserved + 1'h1, // [4] mprven + 1'h0, // [3] nmip // non-maskable interrupt pending + 1'h0, // [2] step + 2'h3}; // [1:0] prv (machine mode) + + Reg #(Data) rg_dcsr <- mkReg (zeroExtend (dcsr_reset_value)); + Reg #(Data) rg_dpc <- mkReg (truncate (soc_map_struct.pc_reset_value)); + Reg #(Data) rg_dscratch0 <- mkRegU; + Reg #(Data) rg_dscratch1 <- mkRegU; +`endif + `ifdef SECURITY // sanctum machine CSRs @@ -607,6 +651,14 @@ module mkCsrFile #(Data hartid)(CsrFile); CSRmspec: mspec_csr; CSRtrng: trng_csr; `endif + +`ifdef INCLUDE_GDB_CONTROL + CSRdcsr: rg_dcsr; // TODO: take NMI into account (cf. Piccolo/Flute) + CSRdpc: rg_dpc; + CSRdscratch0: rg_dscratch0; + CSRdscratch1: rg_dscratch1; +`endif + default: readOnlyReg(64'b0); endcase); endfunction @@ -856,4 +908,40 @@ module mkCsrFile #(Data hartid)(CsrFile); method doPerfStats = stats_module.doPerfStats; method sendDoStats = stats_module.sendDoStats; method recvDoStats = stats_module.recvDoStats; + + // ---------------- + // Bluespec: + // Methods when Debug Module is present + +`ifdef INCLUDE_GDB_CONTROL + // Read dpc + method Addr dpc_read (); + return rg_dpc; + endmethod + + // Update dpc + method Action dpc_write (Addr pc); + rg_dpc <= pc; + endmethod + + // Check whether to enter Debug Mode based on dcsr.{ebreakm, ebreaks, ebreaku} + method Bool dcsr_stop_for_break; + return case (prv_reg) + prvM: (rg_dcsr [15] == 1'b1); + prvS: (rg_dcsr [13] == 1'b1); + prvU: (rg_dcsr [12] == 1'b1); + endcase; + endmethod + + // Check whether to enter Debug Mode based on dcsr.step + method Bool dcsr_stop_for_step; + return (rg_dcsr [2] == 1'b1); + endmethod + + // Update 'cause' in DCSR + method Action dcsr_cause_write (Bit #(3) dcsr_cause); + rg_dcsr <= { 32'b0, rg_dcsr [31:9], dcsr_cause, rg_dcsr [5:2], prv_reg }; + endmethod +`endif + endmodule diff --git a/src_Core/CPU/Proc.bsv b/src_Core/CPU/Proc.bsv index f485003..900b9f8 100644 --- a/src_Core/CPU/Proc.bsv +++ b/src_Core/CPU/Proc.bsv @@ -92,6 +92,31 @@ import TV_Info :: *; // ================================================================ +// Major States of CPU + +typedef enum {CPU_RESET1, + CPU_RESET2, + +`ifdef INCLUDE_GDB_CONTROL + CPU_GDB_PAUSING, // On GDB breakpoint, while waiting for fence completion +`endif + CPU_DEBUG_MODE, // Stopped (normally for debugger) + CPU_RUNNING // Normal operation + } CPU_State +deriving (Eq, Bits, FShow); + +function Bool fn_is_running (CPU_State cpu_state); + return ( (cpu_state != CPU_RESET1) + && (cpu_state != CPU_RESET2) +`ifdef INCLUDE_GDB_CONTROL + && (cpu_state != CPU_GDB_PAUSING) + && (cpu_state != CPU_DEBUG_MODE) +`endif + ); +endfunction + +// ================================================================ + (* synthesize *) module mkProc (Proc_IFC); @@ -108,6 +133,11 @@ module mkProc (Proc_IFC); // Verbosity: 0=quiet; 1=instruction trace; 2=more detail Reg #(Bit #(4)) cfg_verbosity <- mkConfigReg (0); + // ---------------- + // Major CPU states + + Reg #(CPU_State) rg_state <- mkReg (CPU_RESET1); + // ---------------- // Reset requests and responses (TODO: to be implemented) @@ -258,6 +288,8 @@ module mkProc (Proc_IFC); mmio_axi4_adapter.reset; f_reset_rsps.enq (?); + + rg_state <= CPU_RUNNING; endrule // ---------------- @@ -287,6 +319,122 @@ module mkProc (Proc_IFC); end endrule + // ================================================================ + // ================================================================ + // ================================================================ + // DEBUGGER ACCESS + +`ifdef INCLUDE_GDB_CONTROL + + // ---------------- + // Debug Module Run (resume) control + + // Run command when in debug mode + rule rl_debug_run ((f_run_halt_reqs.first == True) + && (! f_gpr_reqs.notEmpty) + && (! f_fpr_reqs.notEmpty) + && (! f_csr_reqs.notEmpty) + && (rg_state == CPU_DEBUG_MODE)); + // if (cfg_verbosity > 1) + $display ("%0d: %m.rl_debug_run", cur_cycle); + + f_run_halt_reqs.deq; + core[0].resume_from_debug_mode; + rg_state <= CPU_RUNNING; + + // Notify debugger that we've started running + f_run_halt_rsps.enq (True); + endrule + + // Run command when already running + rule rl_debug_run_redundant ((f_run_halt_reqs.first == True) + && (! f_gpr_reqs.notEmpty) + && (! f_fpr_reqs.notEmpty) + && (! f_csr_reqs.notEmpty) + && fn_is_running (rg_state)); + // if (cfg_verbosity > 1) + $display ("%0d: %m.rl_debug_run_redundant", cur_cycle); + + f_run_halt_reqs.deq; + + // Notify debugger that we're running + f_run_halt_rsps.enq (True); + endrule + + // ---------------- + // Debug Module Halt control + + rule rl_debug_halt ((f_run_halt_reqs.first == False) && fn_is_running (rg_state)); + // if (cfg_verbosity > 1) + $display ("%0d: %m.rl_debug_halt", cur_cycle); + + f_run_halt_reqs.deq; + + // Debugger 'halt' request (e.g., GDB '^C' command) + core[0].halt_to_debug_mode_req; + + rg_state <= CPU_GDB_PAUSING; + endrule + + rule rl_debug_halted ((rg_state == CPU_GDB_PAUSING) && core [0].is_debug_halted); + // Notify debugger that we've halted + f_run_halt_rsps.enq (False); + // Stop executing rules until ready to restart from debugger + rg_state <= CPU_DEBUG_MODE; + + // if (cfg_verbosity > 1) + $display ("%0d: %m.rl_debug_halted", cur_cycle); + endrule + + rule rl_debug_halt_redundant ((f_run_halt_reqs.first == False) && (! fn_is_running (rg_state))); + // if (cfg_verbosity > 1) + $display ("%0d: %m.rl_debug_halt_redundant", cur_cycle); + + f_run_halt_reqs.deq; + + // Notify debugger that we've 'halted' + f_run_halt_rsps.enq (False); + + $display ("%0d: %m.rl_debug_halt_redundant: CPU already halted; state = ", cur_cycle, fshow (rg_state)); + endrule + + // ---------------- + // Debug Module CSR read/write + + rule rl_debug_read_csr ((rg_state == CPU_DEBUG_MODE) && (! f_csr_reqs.first.write)); + let req <- pop (f_csr_reqs); + Bit #(12) csr_addr = req.address; + let data = core [0].csr_read (csr_addr); + let rsp = DM_CPU_Rsp {ok: True, data: data}; + f_csr_rsps.enq (rsp); + // if (cur_verbosity > 1) + $display ("%m.rl_debug_read_csr: csr %0d => 0x%0h", + csr_addr, data); + endrule + + rule rl_debug_write_csr ((rg_state == CPU_DEBUG_MODE) && f_csr_reqs.first.write); + let req <- pop (f_csr_reqs); + Bit #(12) csr_addr = req.address; + let data = req.data; + core [0].csr_write (csr_addr, data); + let rsp = DM_CPU_Rsp {ok: True, data: ?}; + f_csr_rsps.enq (rsp); + + // if (cur_verbosity > 1) + $display ("%m.rl_debug_write_csr: csr 0x%0h <= 0x%0h", csr_addr, data); + endrule + + rule rl_debug_csr_access_busy (rg_state != CPU_DEBUG_MODE); + let req <- pop (f_csr_reqs); + let rsp = DM_CPU_Rsp {ok: False, data: ?}; + f_csr_rsps.enq (rsp); + + // if (cur_verbosity > 1) + $display ("%m.rl_debug_csr_access_busy"); + endrule + +`endif + // ================================================================ // ================================================================ // ================================================================ @@ -305,7 +453,7 @@ module mkProc (Proc_IFC); mmioPlatform.start (tohostAddr, fromhostAddr); - $display ("Proc.start: startpc = 0x%0h, tohostAddr = 0x%0h, fromhostAddr = %0h", + $display ("%m.start: startpc = 0x%0h, tohostAddr = 0x%0h, fromhostAddr = %0h", startpc, tohostAddr, fromhostAddr); endmethod diff --git a/src_Core/RISCY_OOO/procs/RV64G_OOO/CommitStage.bsv b/src_Core/RISCY_OOO/procs/RV64G_OOO/CommitStage.bsv index dc142d3..d4149b6 100644 --- a/src_Core/RISCY_OOO/procs/RV64G_OOO/CommitStage.bsv +++ b/src_Core/RISCY_OOO/procs/RV64G_OOO/CommitStage.bsv @@ -39,6 +39,8 @@ import StoreBuffer::*; import VerificationPacket::*; import RenameDebugIF::*; +import Cur_Cycle :: *; + typedef struct { // info about the inst blocking at ROB head Addr pc; @@ -112,6 +114,18 @@ interface CommitStage; // rename debug method Action startRenameDebug; interface Get#(RenameErrInfo) renameErr; + +`ifdef INCLUDE_GDB_CONTROL + // Request halt into debug mode + method Action halt_to_debug_mode_req; + + (* always_ready *) + // Becomes true when pipeline is halted + method Bool is_debug_halted; + + method Action resume_from_debug_mode; +`endif + endinterface // we apply actions the end of commit rule @@ -122,6 +136,17 @@ typedef struct { Trap trap; } CommitTrap deriving(Bits, Eq, FShow); +// Bluespec: for debugger run-control +typedef enum { + Run_State_RUNNING // Normal state +`ifdef INCLUDE_GDB_CONTROL + , Run_State_DEBUGGER_HALT // When ready to halt for debugger after stop/step + , Run_State_DEBUGGER_HALT_FLUSH // When flushing state during debugger halt + , Run_State_DEBUGGER_HALTED // When halted for debugger +`endif + } Run_State +deriving (Eq, FShow, Bits); + module mkCommitStage#(CommitInput inIfc)(CommitStage); Bool verbose = False; @@ -129,6 +154,17 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); Integer verbosity = 1; Reg #(Bit #(64)) rg_instret <- mkReg (0); + // Bluespec: for debugger run-control + Reg #(Run_State) rg_run_state <- mkReg (Run_State_RUNNING); + +`ifdef INCLUDE_GDB_CONTROL + // rg_stop_req is set True when debugger requests a stop (e.g., GDB ^C) + Reg #(Bool) rg_stop_req <- mkReg (False); + // When going into debug mode, these hold the cause for the break and the resume-PC + Reg #(Bit #(3)) rg_debug_cause <- mkRegU; + Reg #(Addr) rg_debug_pc <- mkRegU; +`endif + // func units ReorderBufferSynth rob = inIfc.robIfc; RegRenamingTable regRenamingTable = inIfc.rtIfc; @@ -338,15 +374,57 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); endaction endfunction +`ifdef INCLUDE_GDB_CONTROL + // ================================================================ + // Bluespec: debugger run-control + // Every time an instruction commits, we check this boolean + // to decide whether we continue running of stop. + + // We halt after committing an instruction if: + // the debugger requested a stop + // or dcsr.step is True + + Maybe #(Bit #(3)) m_debug_stop_step_cause = ( rg_stop_req + ? tagged Valid 3 // Debug Module haltreq + : (csrf.dcsr_stop_for_step + ? tagged Valid 4 // dcsr.step + : tagged Invalid)); + + // ================================================================ + + /* + // Bluespec: debugger run-control + // Rule cond should be identical to doCommitTrap_flush's cond + // except for fn_ebreak_to_debug_mode() + rule doCommitTrap_ebreak_to_debug_mode( + (rg_run_state == Run_State_RUNNING) &&& // Bluespec: debugger run-control + !isValid(commitTrap) &&& + rob.deqPort[0].deq_data.trap matches tagged Valid .trap &&& + fn_ebreak_to_debug_mode (trap) // Bluespec: debugger run-control + ); + let x = rob.deqPort[0].deq_data; + rg_run_state <= Run_State_DEBUGGER_HALT; + rg_debug_pc <= x.pc; + rg_debug_cause <= 1; // cause: EBREAK + endrule + */ +`endif + + // ================================================================ + // TODO Currently we don't check spec bits == 0 when we commit an // instruction. This is because killings of wrong path instructions are // done in a single cycle. However, when we make killings distributed or // pipelined, then we need to check spec bits at commit port. rule doCommitTrap_flush( +`ifdef INCLUDE_GDB_CONTROL + (rg_run_state == Run_State_RUNNING) &&& // Bluespec: debugger run-control +`endif !isValid(commitTrap) &&& rob.deqPort[0].deq_data.trap matches tagged Valid .trap ); + $display ("%0d: %m.CommitStage.rule_doCommitTrap_flush", cur_cycle); rob.deqPort[0].deq; let x = rob.deqPort[0].deq_data; if(verbose) $display("[doCommitTrap] ", fshow(x)); @@ -404,7 +482,15 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); doAssert(x.spec_bits == 0, "cannot have spec bits"); endrule - rule doCommitTrap_handle(commitTrap matches tagged Valid .trap); + // This rule's condition is enabled only by the previous rule, doCommitTrap_flush + rule doCommitTrap_handle( +`ifdef INCLUDE_GDB_CONTROL + (rg_run_state == Run_State_RUNNING) &&& // Bluespec: debugger run-control +`endif + commitTrap matches tagged Valid .trap); + + $display ("%0d: %m.CommitStage.rule_doCommitTrap_handle", cur_cycle); + // reset commitTrap commitTrap <= Invalid; @@ -413,9 +499,31 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); inIfc.commitCsrInstOrInterrupt; end - // trap handling & redirect - let new_pc <- csrf.trap(trap.trap, trap.pc, trap.addr); - inIfc.redirectPc(new_pc); +`ifdef INCLUDE_GDB_CONTROL + if ((trap.trap == tagged Exception Breakpoint) && csrf.dcsr_stop_for_break) begin + // Don't handle the trap + rg_run_state <= Run_State_DEBUGGER_HALTED; + // Record debug CSRs for later Debugger query and later resumption + csrf.dcsr_cause_write (1); // EBREAK dcsr.cause + csrf.dpc_write (trap.pc); // Where we'll resume on 'continue' + end + else begin + // Handle the trap and redirect + let new_pc <- csrf.trap(trap.trap, trap.pc, trap.addr); + inIfc.redirectPc (new_pc); + + if (m_debug_stop_step_cause matches tagged Valid .cause) begin + rg_run_state <= Run_State_DEBUGGER_HALTED; + // Record debug CSRs for later Debugger query and later resumption + csrf.dcsr_cause_write (cause); + csrf.dpc_write (new_pc); // Where we'll resume on 'continue' + end + end +`else + // trap handling & redirect + let new_pc <- csrf.trap(trap.trap, trap.pc, trap.addr); + inIfc.redirectPc(new_pc); +`endif // system consistency // TODO spike flushes TLB here, but perhaps it is because spike's TLB @@ -427,6 +535,9 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); // commit misspeculated load rule doCommitKilledLd( +`ifdef INCLUDE_GDB_CONTROL + (rg_run_state == Run_State_RUNNING) &&& // Bluespec: debugger run-control +`endif !isValid(commitTrap) &&& !isValid(rob.deqPort[0].deq_data.trap) &&& rob.deqPort[0].deq_data.ldKilled matches tagged Valid .killBy @@ -437,7 +548,19 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); // kill everything, redirect, and increment epoch inIfc.killAll; - inIfc.redirectPc(x.pc); + +`ifdef INCLUDE_GDB_CONTROL + // Bluespec: debug run-control + doAssert (x.iType != Ebreak, "CommitStage.rule_doCommitKilledLd iType should not be Ebreak"); + if (m_debug_stop_step_cause matches tagged Valid .cause) begin + rg_run_state <= Run_State_DEBUGGER_HALT_FLUSH; + rg_debug_pc <= x.pc; + rg_debug_cause <= cause; + end + else +`endif + inIfc.redirectPc(x.pc); // original (no debugger) + inIfc.incrementEpoch; // the killed Ld should have claimed phy reg, we should not commit it; @@ -461,6 +584,9 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); // commit system inst rule doCommitSystemInst( +`ifdef INCLUDE_GDB_CONTROL + (rg_run_state == Run_State_RUNNING) && // Bluespec: debugger run-control +`endif !isValid(commitTrap) && !isValid(rob.deqPort[0].deq_data.trap) && !isValid(rob.deqPort[0].deq_data.ldKilled) && @@ -510,7 +636,18 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); else if(x.iType == Mret) begin next_pc <- csrf.mret; end - inIfc.redirectPc(next_pc); + +`ifdef INCLUDE_GDB_CONTROL + // Bluespec: debug run-control + doAssert (x.iType != Ebreak, "CommitStage.rule_doCommitSystemInst iType should not be Ebreak"); + if (m_debug_stop_step_cause matches tagged Valid .cause) begin + rg_run_state <= Run_State_DEBUGGER_HALT_FLUSH; + rg_debug_pc <= next_pc; + rg_debug_cause <= cause; + end + else +`endif + inIfc.redirectPc(next_pc); // original (no debugger) // rename stage only sends out system inst when ROB is empty, so no // need to flush ROB again @@ -567,6 +704,9 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); // Lr/Sc/Amo/MMIO cannot proceed to executed until we notify LSQ that it // has reached the commit stage rule notifyLSQCommit( +`ifdef INCLUDE_GDB_CONTROL + (rg_run_state == Run_State_RUNNING) && // Bluespec: debugger run-control +`endif !isValid(commitTrap) && !isValid(rob.deqPort[0].deq_data.trap) && !isValid(rob.deqPort[0].deq_data.ldKilled) && @@ -585,6 +725,9 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); // commit normal: fire when at least one commit can be done rule doCommitNormalInst( +`ifdef INCLUDE_GDB_CONTROL + (rg_run_state == Run_State_RUNNING) && // Bluespec: debugger run-control +`endif !isValid(commitTrap) && !isValid(rob.deqPort[0].deq_data.trap) && !isValid(rob.deqPort[0].deq_data.ldKilled) && @@ -596,6 +739,10 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); // 2. inst is not ready to commit Bool stop = False; + // Bluespec: debugger run-control + // 3. one instr is committed and debugger halt is requested (due to stop or step) + Bool stop_for_debugger = False; + // We merge writes on FPU csr and apply writes at the end of the rule Bit#(5) fflags = 0; Bool will_dirty_fpu_state = False; @@ -620,7 +767,8 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); // compute what actions to take for(Integer i = 0; i < valueof(SupSize); i = i+1) begin - if(!stop && rob.deqPort[i].canDeq) begin + + if(!stop && (! stop_for_debugger) && rob.deqPort[i].canDeq) begin let x = rob.deqPort[i].deq_data; let inst_tag = rob.deqPort[i].getDeqInstTag; @@ -673,6 +821,35 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); comUserInstCnt = comUserInstCnt + 1; // user space inst end +`ifdef INCLUDE_GDB_CONTROL + if (m_debug_stop_step_cause matches tagged Valid .cause + &&& (comInstCnt != 0)) + begin + $display ("%0d: %m.CommitStage.rule_doCommitNormalInst: Stopping for debugger", cur_cycle); + stop_for_debugger = True; + + // Compute next PC in case of debugger stop. + // Note: AluExePipeline.rule_doFinishAlu does inIfc.rob_setExecuted (... x.controlFlow) + // which updates the rob entry's ppc_vaddr_csrData field with actual + // branch/jump targets, which we retrieve here. + Addr next_pc = ?; + if (x.ppc_vaddr_csrData matches tagged PPC .addr + &&& ((x.iType == J) || (x.iType == Jr) || (x.iType == Br))) + // JAL, JALR, BRANCH + next_pc = addr; + else if (x.orig_inst [1:0] == 2'b11) + // RV32I RV64I + next_pc = x.pc + 4; + else + // RVC + next_pc = x.pc + 2; + + rg_run_state <= Run_State_DEBUGGER_HALT_FLUSH; + rg_debug_pc <= next_pc; + rg_debug_cause <= cause; + end +`endif + `ifdef PERF_COUNT // performance counter case(x.iType) @@ -688,7 +865,7 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); `endif end end - end + end // for-loop for superscalar width rg_instret <= rg_instret + instret; // write FPU csr @@ -736,6 +913,47 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); `endif endrule +`ifdef INCLUDE_GDB_CONTROL + // ================================================================ + // Rules to move into debug mode + + // This rule is like rule doCommitTrap_flush, i.e., a debugger-halt is like an interrupt + rule rl_enter_debug_mode_flush (rg_run_state == Run_State_DEBUGGER_HALT_FLUSH); + $write ("%0d: %m.commitStage.rl_enter_debug_mode:", cur_cycle); + if (rg_debug_cause == 1) $display (" EBREAK"); + else if (rg_debug_cause == 3) $display (" Debugger halt request"); + else if (rg_debug_cause == 4) $display (" Halt after step"); + else $display (" Unknown cause %0d", rg_debug_cause); + + // flush everything. Only increment epoch and stall fetch when we haven + // not done it yet (we may have already done them at rename stage) + inIfc.killAll; + inIfc.incrementEpoch; + inIfc.setFetchWaitRedirect; + + rg_run_state <= Run_State_DEBUGGER_HALT; + endrule + + // This rule is like rule doCommitTrap_handle, except we don't redirect + rule rl_enter_debug_mode_halt (rg_run_state == Run_State_DEBUGGER_HALT); + // system consistency + // TODO spike flushes TLB here, but perhaps it is because spike's TLB + // does not include prv info, and it has to flush when prv changes. + // XXX As approximation, Trap may cause context switch, so flush for + // security + makeSystemConsistent(False, True, False); + + // Record debug CSRs for later Debugger query and later resumption + csrf.dcsr_cause_write (rg_debug_cause); // reason for entering debug mode + csrf.dpc_write (rg_debug_pc); // Where we'll resume on 'continue' + + // Go to HALTED state where no rule fires. + // To re-start, mkCore must call restart method. + rg_run_state <= Run_State_DEBUGGER_HALTED; + endrule + + // ================================================================ +`endif method Data getPerf(ComStagePerfType t); return (case(t) @@ -785,4 +1003,24 @@ module mkCommitStage#(CommitInput inIfc)(CommitStage); endmethod interface renameErr = nullGet; `endif + +`ifdef INCLUDE_GDB_CONTROL + // Request halt into debug mode + method Action halt_to_debug_mode_req () if (rg_run_state == Run_State_RUNNING); + $display ("%0d: %m.commitStage.halt_to_debug_mode_req", cur_cycle); + rg_stop_req <= True; + endmethod + + // Becomes true when pipeline is halted + method Bool is_debug_halted; + return (rg_run_state == Run_State_DEBUGGER_HALTED); + endmethod + + method Action resume_from_debug_mode () if (rg_run_state == Run_State_DEBUGGER_HALTED); + $display ("%0d: %m.commitStage.resume_from_debug_mode", cur_cycle); + rg_stop_req <= False; + rg_run_state <= Run_State_RUNNING; + endmethod +`endif + endmodule diff --git a/src_Core/RISCY_OOO/procs/RV64G_OOO/FetchStage.bsv b/src_Core/RISCY_OOO/procs/RV64G_OOO/FetchStage.bsv index 635d904..31118ff 100644 --- a/src_Core/RISCY_OOO/procs/RV64G_OOO/FetchStage.bsv +++ b/src_Core/RISCY_OOO/procs/RV64G_OOO/FetchStage.bsv @@ -50,6 +50,8 @@ import CCTypes::*; import L1CoCache::*; import MMIOInst::*; +import Cur_Cycle :: *; + // ================================================================ // For fv_decode_C function and related types and definitions @@ -90,6 +92,10 @@ interface FetchStage; // performance interface Perf#(DecStagePerfType) perf; + +`ifdef INCLUDE_GDB_CONTROL + method Action resume_from_debug_mode (Addr new_pc); +`endif endinterface typedef struct { @@ -984,6 +990,7 @@ module mkFetchStage(FetchStage); interface mmioIfc = mmio.toCore; method Action start(Addr start_pc); + $display ("%0d: %m.start (start_pc = 0x%0h)", cur_cycle, start_pc); pc_reg[0] <= start_pc; started <= True; waitForRedirect <= False; @@ -991,6 +998,7 @@ module mkFetchStage(FetchStage); endmethod method Action stop(); started <= False; + $display ("%0d: %m.stop", cur_cycle); endmethod method Action setWaitRedirect; @@ -1095,5 +1103,24 @@ module mkFetchStage(FetchStage); method Bool respValid = perfReqQ.notEmpty; `endif endinterface + +`ifdef INCLUDE_GDB_CONTROL + method Action resume_from_debug_mode (Addr new_pc); + if (verbose) + $display("resume_from_debug_mode: newpc %h, old f_main_epoch %d, new f_main_epoch %d", + new_pc, f_main_epoch, f_main_epoch + 1); + pc_reg[pc_redirect_port] <= new_pc; + f_main_epoch <= (f_main_epoch == fromInteger(valueOf(NumEpochs)-1)) ? 0 : f_main_epoch + 1; + ehr_pending_straddle[1] <= tagged Invalid; + // redirect comes, stop stalling for redirect + waitForRedirect <= False; + setWaitRedirect_redirect_conflict.wset(?); // conflict with setWaitForRedirect + + // this redirect may be caused by a trap/system inst in commit stage + // we conservatively set wait for flush TODO make this an input parameter + // TODO: do we need this? waitForFlush <= True; + endmethod +`endif + endmodule diff --git a/src_Core/RISCY_OOO/procs/lib/ProcTypes.bsv b/src_Core/RISCY_OOO/procs/lib/ProcTypes.bsv index a3ce618..b4f2d5f 100644 --- a/src_Core/RISCY_OOO/procs/lib/ProcTypes.bsv +++ b/src_Core/RISCY_OOO/procs/lib/ProcTypes.bsv @@ -269,6 +269,14 @@ typedef enum { // sanctum user CSR CSRtrng = 12'hcc0, // random number for secure boot `endif + +`ifdef INCLUDE_GDB_CONTROL + CSRdcsr = 12'h7B0, // Debug control and status + CSRdpc = 12'h7B1, // Debug PC + CSRdscratch0 = 12'h7B2, // Debug scratch0 + CSRdscratch1 = 12'h7B3, // Debug scratch1 +`endif + // CSR that catches all the unimplemented CSRs. To avoid exception on this, // make it a user non-standard read/write CSR. CSRnone = 12'h8ff