| /* Copyright 2018 ETH Zurich and University of Bologna. |
| * Copyright and related rights are licensed under the Solderpad Hardware |
| * License, Version 0.51 (the “License”); you may not use this file except in |
| * compliance with the License. You may obtain a copy of the License at |
| * http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law |
| * or agreed to in writing, software, hardware and materials distributed under |
| * this License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR |
| * CONDITIONS OF ANY KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations under the License. |
| * |
| * File: dm_mem.sv |
| * Author: Florian Zaruba <zarubaf@iis.ee.ethz.ch> |
| * Date: 11.7.2018 |
| * |
| * Description: Memory module for execution-based debug clients |
| * |
| */ |
| |
| module dm_mem #( |
| parameter int unsigned NrHarts = 1, |
| parameter int unsigned BusWidth = 32, |
| parameter logic [NrHarts-1:0] SelectableHarts = {NrHarts{1'b1}}, |
| parameter int unsigned DmBaseAddress = '0 |
| ) ( |
| input logic clk_i, // Clock |
| input logic rst_ni, // debug module reset |
| |
| output logic [NrHarts-1:0] debug_req_o, |
| input logic [19:0] hartsel_i, |
| // from Ctrl and Status register |
| input logic [NrHarts-1:0] haltreq_i, |
| input logic [NrHarts-1:0] resumereq_i, |
| input logic clear_resumeack_i, |
| |
| // state bits |
| output logic [NrHarts-1:0] halted_o, // hart acknowledge halt |
| output logic [NrHarts-1:0] resuming_o, // hart is resuming |
| |
| input logic [dm::ProgBufSize-1:0][31:0] progbuf_i, // program buffer to expose |
| |
| input logic [dm::DataCount-1:0][31:0] data_i, // data in |
| output logic [dm::DataCount-1:0][31:0] data_o, // data out |
| output logic data_valid_o, // data out is valid |
| // abstract command interface |
| input logic cmd_valid_i, |
| input dm::command_t cmd_i, |
| output logic cmderror_valid_o, |
| output dm::cmderr_e cmderror_o, |
| output logic cmdbusy_o, |
| // data interface |
| |
| // SRAM interface |
| input logic req_i, |
| input logic we_i, |
| input logic [BusWidth-1:0] addr_i, |
| input logic [BusWidth-1:0] wdata_i, |
| input logic [BusWidth/8-1:0] be_i, |
| output logic [BusWidth-1:0] rdata_o |
| ); |
| localparam int unsigned DbgAddressBits = 12; |
| localparam int unsigned HartSelLen = (NrHarts == 1) ? 1 : $clog2(NrHarts); |
| localparam int unsigned NrHartsAligned = 2**HartSelLen; |
| localparam int unsigned MaxAar = (BusWidth == 64) ? 4 : 3; |
| localparam bit HasSndScratch = (DmBaseAddress != 0); |
| // Depending on whether we are at the zero page or not we either use `x0` or `x10/a0` |
| localparam logic [4:0] LoadBaseAddr = (DmBaseAddress == 0) ? 5'd0 : 5'd10; |
| |
| localparam logic [DbgAddressBits-1:0] DataBaseAddr = (dm::DataAddr); |
| localparam logic [DbgAddressBits-1:0] DataEndAddr = (dm::DataAddr + 4*dm::DataCount - 1); |
| localparam logic [DbgAddressBits-1:0] ProgBufBaseAddr = (dm::DataAddr - 4*dm::ProgBufSize); |
| localparam logic [DbgAddressBits-1:0] ProgBufEndAddr = (dm::DataAddr - 1); |
| localparam logic [DbgAddressBits-1:0] AbstractCmdBaseAddr = (ProgBufBaseAddr - 4*10); |
| localparam logic [DbgAddressBits-1:0] AbstractCmdEndAddr = (ProgBufBaseAddr - 1); |
| |
| localparam logic [DbgAddressBits-1:0] WhereToAddr = 'h300; |
| localparam logic [DbgAddressBits-1:0] FlagsBaseAddr = 'h400; |
| localparam logic [DbgAddressBits-1:0] FlagsEndAddr = 'h7FF; |
| |
| localparam logic [DbgAddressBits-1:0] HaltedAddr = 'h100; |
| localparam logic [DbgAddressBits-1:0] GoingAddr = 'h104; |
| localparam logic [DbgAddressBits-1:0] ResumingAddr = 'h108; |
| localparam logic [DbgAddressBits-1:0] ExceptionAddr = 'h10C; |
| |
| logic [dm::ProgBufSize/2-1:0][63:0] progbuf; |
| logic [7:0][63:0] abstract_cmd; |
| logic [NrHarts-1:0] halted_d, halted_q; |
| logic [NrHarts-1:0] resuming_d, resuming_q; |
| logic resume, go, going; |
| |
| logic exception; |
| logic unsupported_command; |
| |
| logic [63:0] rom_rdata; |
| logic [63:0] rdata_d, rdata_q; |
| logic word_enable32_q; |
| |
| // this is needed to avoid lint warnings related to array indexing |
| // resize hartsel to valid range |
| logic [HartSelLen-1:0] hartsel, wdata_hartsel; |
| |
| assign hartsel = hartsel_i[HartSelLen-1:0]; |
| assign wdata_hartsel = wdata_i[HartSelLen-1:0]; |
| |
| logic [NrHartsAligned-1:0] resumereq_aligned, haltreq_aligned, |
| halted_d_aligned, halted_q_aligned, |
| halted_aligned, resumereq_wdata_aligned, |
| resuming_d_aligned, resuming_q_aligned; |
| |
| assign resumereq_aligned = NrHartsAligned'(resumereq_i); |
| assign haltreq_aligned = NrHartsAligned'(haltreq_i); |
| assign resumereq_wdata_aligned = NrHartsAligned'(resumereq_i); |
| |
| assign halted_q_aligned = NrHartsAligned'(halted_q); |
| assign halted_d = NrHarts'(halted_d_aligned); |
| assign resuming_q_aligned = NrHartsAligned'(resuming_q); |
| assign resuming_d = NrHarts'(resuming_d_aligned); |
| |
| // distinguish whether we need to forward data from the ROM or the FSM |
| // latch the address for this |
| logic fwd_rom_d, fwd_rom_q; |
| dm::ac_ar_cmd_t ac_ar; |
| |
| // Abstract Command Access Register |
| assign ac_ar = dm::ac_ar_cmd_t'(cmd_i.control); |
| assign debug_req_o = haltreq_i; |
| assign halted_o = halted_q; |
| assign resuming_o = resuming_q; |
| |
| // reshape progbuf |
| assign progbuf = progbuf_i; |
| |
| typedef enum logic [1:0] { Idle, Go, Resume, CmdExecuting } state_e; |
| state_e state_d, state_q; |
| |
| // hart ctrl queue |
| always_comb begin : p_hart_ctrl_queue |
| cmderror_valid_o = 1'b0; |
| cmderror_o = dm::CmdErrNone; |
| state_d = state_q; |
| go = 1'b0; |
| resume = 1'b0; |
| cmdbusy_o = 1'b1; |
| |
| unique case (state_q) |
| Idle: begin |
| cmdbusy_o = 1'b0; |
| if (cmd_valid_i && halted_q_aligned[hartsel] && !unsupported_command) begin |
| // give the go signal |
| state_d = Go; |
| end else if (cmd_valid_i) begin |
| // hart must be halted for all requests |
| cmderror_valid_o = 1'b1; |
| cmderror_o = dm::CmdErrorHaltResume; |
| end |
| // CSRs want to resume, the request is ignored when the hart is |
| // requested to halt or it didn't clear the resuming_q bit before |
| if (resumereq_aligned[hartsel] && !resuming_q_aligned[hartsel] && |
| !haltreq_aligned[hartsel] && halted_q_aligned[hartsel]) begin |
| state_d = Resume; |
| end |
| end |
| |
| Go: begin |
| // we are already busy here since we scheduled the execution of a program |
| cmdbusy_o = 1'b1; |
| go = 1'b1; |
| // the thread is now executing the command, track its state |
| if (going) begin |
| state_d = CmdExecuting; |
| end |
| end |
| |
| Resume: begin |
| cmdbusy_o = 1'b1; |
| resume = 1'b1; |
| if (resuming_q_aligned[hartsel]) begin |
| state_d = Idle; |
| end |
| end |
| |
| CmdExecuting: begin |
| cmdbusy_o = 1'b1; |
| go = 1'b0; |
| // wait until the hart has halted again |
| if (halted_aligned[hartsel]) begin |
| state_d = Idle; |
| end |
| end |
| |
| //default: ; |
| endcase |
| |
| // only signal once that cmd is unsupported so that we can clear cmderr |
| // in subsequent writes to abstractcs |
| if (unsupported_command && cmd_valid_i) begin |
| cmderror_valid_o = 1'b1; |
| cmderror_o = dm::CmdErrNotSupported; |
| end |
| |
| if (exception) begin |
| cmderror_valid_o = 1'b1; |
| cmderror_o = dm::CmdErrorException; |
| end |
| end |
| |
| // word mux for 32bit and 64bit buses |
| logic [63:0] word_mux; |
| assign word_mux = (fwd_rom_q) ? rom_rdata : rdata_q; |
| |
| if (BusWidth == 64) begin : gen_word_mux64 |
| assign rdata_o = word_mux; |
| end else begin : gen_word_mux32 |
| assign rdata_o = (word_enable32_q) ? word_mux[32 +: 32] : word_mux[0 +: 32]; |
| end |
| |
| // read/write logic |
| logic [63:0] data_bits; |
| logic [7:0][7:0] rdata; |
| always_comb begin : p_rw_logic |
| |
| halted_d_aligned = NrHartsAligned'(halted_q); |
| resuming_d_aligned = NrHartsAligned'(resuming_q); |
| rdata_d = rdata_q; |
| // convert the data in bits representation |
| data_bits = data_i; |
| rdata = '0; |
| |
| // write data in csr register |
| data_valid_o = 1'b0; |
| exception = 1'b0; |
| halted_aligned = '0; |
| going = 1'b0; |
| |
| // The resume ack signal is lowered when the resume request is deasserted |
| if (clear_resumeack_i) begin |
| resuming_d_aligned[hartsel] = 1'b0; |
| end |
| // we've got a new request |
| if (req_i) begin |
| // this is a write |
| if (we_i) begin |
| unique case (addr_i[DbgAddressBits-1:0]) inside |
| HaltedAddr: begin |
| halted_aligned[wdata_hartsel] = 1'b1; |
| halted_d_aligned[wdata_hartsel] = 1'b1; |
| end |
| GoingAddr: begin |
| going = 1'b1; |
| end |
| ResumingAddr: begin |
| // clear the halted flag as the hart resumed execution |
| halted_d_aligned[wdata_hartsel] = 1'b0; |
| // set the resuming flag which needs to be cleared by the debugger |
| resuming_d_aligned[wdata_hartsel] = 1'b1; |
| end |
| // an exception occurred during execution |
| ExceptionAddr: exception = 1'b1; |
| // core can write data registers |
| [DataBaseAddr:DataEndAddr]: begin |
| data_valid_o = 1'b1; |
| for (int i = 0; i < $bits(be_i); i++) begin |
| if (be_i[i]) begin |
| data_bits[i*8+:8] = wdata_i[i*8+:8]; |
| end |
| end |
| end |
| default ; |
| endcase |
| |
| // this is a read |
| end else begin |
| unique case (addr_i[DbgAddressBits-1:0]) inside |
| // variable ROM content |
| WhereToAddr: begin |
| // variable jump to abstract cmd, program_buffer or resume |
| if (resumereq_wdata_aligned[wdata_hartsel]) begin |
| rdata_d = {32'b0, dm::jal('0, 21'(dm::ResumeAddress[11:0])-21'(WhereToAddr))}; |
| end |
| |
| // there is a command active so jump there |
| if (cmdbusy_o) begin |
| // transfer not set is shortcut to the program buffer if postexec is set |
| // keep this statement narrow to not catch invalid commands |
| if (cmd_i.cmdtype == dm::AccessRegister && |
| !ac_ar.transfer && ac_ar.postexec) begin |
| rdata_d = {32'b0, dm::jal('0, 21'(ProgBufBaseAddr)-21'(WhereToAddr))}; |
| // this is a legit abstract cmd -> execute it |
| end else begin |
| rdata_d = {32'b0, dm::jal('0, 21'(AbstractCmdBaseAddr)-21'(WhereToAddr))}; |
| end |
| end |
| end |
| |
| [DataBaseAddr:DataEndAddr]: begin |
| rdata_d = { |
| data_i[$clog2(dm::ProgBufSize)'(addr_i[DbgAddressBits-1:3] - |
| DataBaseAddr[DbgAddressBits-1:3] + 1'b1)], |
| data_i[$clog2(dm::ProgBufSize)'(addr_i[DbgAddressBits-1:3] - |
| DataBaseAddr[DbgAddressBits-1:3])] |
| }; |
| end |
| |
| [ProgBufBaseAddr:ProgBufEndAddr]: begin |
| rdata_d = progbuf[$clog2(dm::ProgBufSize)'(addr_i[DbgAddressBits-1:3] - |
| ProgBufBaseAddr[DbgAddressBits-1:3])]; |
| end |
| |
| // two slots for abstract command |
| [AbstractCmdBaseAddr:AbstractCmdEndAddr]: begin |
| // return the correct address index |
| rdata_d = abstract_cmd[3'(addr_i[DbgAddressBits-1:3] - |
| AbstractCmdBaseAddr[DbgAddressBits-1:3])]; |
| end |
| // harts are polling for flags here |
| [FlagsBaseAddr:FlagsEndAddr]: begin |
| // release the corresponding hart |
| if (({addr_i[DbgAddressBits-1:3], 3'b0} - FlagsBaseAddr[DbgAddressBits-1:0]) == |
| (DbgAddressBits'(hartsel) & {{(DbgAddressBits-3){1'b1}}, 3'b0})) begin |
| rdata[DbgAddressBits'(hartsel) & DbgAddressBits'(3'b111)] = {6'b0, resume, go}; |
| end |
| rdata_d = rdata; |
| end |
| default: ; |
| endcase |
| end |
| end |
| |
| data_o = data_bits; |
| end |
| |
| always_comb begin : p_abstract_cmd_rom |
| // this abstract command is currently unsupported |
| unsupported_command = 1'b0; |
| // default memory |
| // if ac_ar.transfer is not set then we can take a shortcut to the program buffer |
| abstract_cmd[0][31:0] = dm::illegal(); |
| // load debug module base address into a0, this is shared among all commands |
| abstract_cmd[0][63:32] = HasSndScratch ? dm::auipc(5'd10, '0) : dm::nop(); |
| // clr lowest 12b -> DM base offset |
| abstract_cmd[1][31:0] = HasSndScratch ? dm::srli(5'd10, 5'd10, 6'd12) : dm::nop(); |
| abstract_cmd[1][63:32] = HasSndScratch ? dm::slli(5'd10, 5'd10, 6'd12) : dm::nop(); |
| abstract_cmd[2][31:0] = dm::nop(); |
| abstract_cmd[2][63:32] = dm::nop(); |
| abstract_cmd[3][31:0] = dm::nop(); |
| abstract_cmd[3][63:32] = dm::nop(); |
| abstract_cmd[4][31:0] = HasSndScratch ? dm::csrr(dm::CSR_DSCRATCH1, 5'd10) : dm::nop(); |
| abstract_cmd[4][63:32] = dm::ebreak(); |
| abstract_cmd[7:5] = '0; |
| |
| // this depends on the command being executed |
| unique case (cmd_i.cmdtype) |
| // -------------------- |
| // Access Register |
| // -------------------- |
| dm::AccessRegister: begin |
| if (32'(ac_ar.aarsize) < MaxAar && ac_ar.transfer && ac_ar.write) begin |
| // store a0 in dscratch1 |
| abstract_cmd[0][31:0] = HasSndScratch ? dm::csrr(dm::CSR_DSCRATCH1, 5'd10) : dm::nop(); |
| // this range is reserved |
| if (ac_ar.regno[15:14] != '0) begin |
| abstract_cmd[0][31:0] = dm::ebreak(); // we leave asap |
| unsupported_command = 1'b1; |
| // A0 access needs to be handled separately, as we use A0 to load |
| // the DM address offset need to access DSCRATCH1 in this case |
| end else if (HasSndScratch && ac_ar.regno[12] && (!ac_ar.regno[5]) && |
| (ac_ar.regno[4:0] == 5'd10)) begin |
| // store s0 in dscratch |
| abstract_cmd[2][31:0] = dm::csrw(dm::CSR_DSCRATCH0, 5'd8); |
| // load from data register |
| abstract_cmd[2][63:32] = dm::load(ac_ar.aarsize, 5'd8, LoadBaseAddr, dm::DataAddr); |
| // and store it in the corresponding CSR |
| abstract_cmd[3][31:0] = dm::csrw(dm::CSR_DSCRATCH1, 5'd8); |
| // restore s0 again from dscratch |
| abstract_cmd[3][63:32] = dm::csrr(dm::CSR_DSCRATCH0, 5'd8); |
| // GPR/FPR access |
| end else if (ac_ar.regno[12]) begin |
| // determine whether we want to access the floating point register or not |
| if (ac_ar.regno[5]) begin |
| abstract_cmd[2][31:0] = |
| dm::float_load(ac_ar.aarsize, ac_ar.regno[4:0], LoadBaseAddr, dm::DataAddr); |
| end else begin |
| abstract_cmd[2][31:0] = |
| dm::load(ac_ar.aarsize, ac_ar.regno[4:0], LoadBaseAddr, dm::DataAddr); |
| end |
| // CSR access |
| end else begin |
| // data register to CSR |
| // store s0 in dscratch |
| abstract_cmd[2][31:0] = dm::csrw(dm::CSR_DSCRATCH0, 5'd8); |
| // load from data register |
| abstract_cmd[2][63:32] = dm::load(ac_ar.aarsize, 5'd8, LoadBaseAddr, dm::DataAddr); |
| // and store it in the corresponding CSR |
| abstract_cmd[3][31:0] = dm::csrw(dm::csr_reg_t'(ac_ar.regno[11:0]), 5'd8); |
| // restore s0 again from dscratch |
| abstract_cmd[3][63:32] = dm::csrr(dm::CSR_DSCRATCH0, 5'd8); |
| end |
| end else if (32'(ac_ar.aarsize) < MaxAar && ac_ar.transfer && !ac_ar.write) begin |
| // store a0 in dscratch1 |
| abstract_cmd[0][31:0] = HasSndScratch ? |
| dm::csrr(dm::CSR_DSCRATCH1, LoadBaseAddr) : |
| dm::nop(); |
| // this range is reserved |
| if (ac_ar.regno[15:14] != '0) begin |
| abstract_cmd[0][31:0] = dm::ebreak(); // we leave asap |
| unsupported_command = 1'b1; |
| // A0 access needs to be handled separately, as we use A0 to load |
| // the DM address offset need to access DSCRATCH1 in this case |
| end else if (HasSndScratch && ac_ar.regno[12] && (!ac_ar.regno[5]) && |
| (ac_ar.regno[4:0] == 5'd10)) begin |
| // store s0 in dscratch |
| abstract_cmd[2][31:0] = dm::csrw(dm::CSR_DSCRATCH0, 5'd8); |
| // read value from CSR into s0 |
| abstract_cmd[2][63:32] = dm::csrr(dm::CSR_DSCRATCH1, 5'd8); |
| // and store s0 into data section |
| abstract_cmd[3][31:0] = dm::store(ac_ar.aarsize, 5'd8, LoadBaseAddr, dm::DataAddr); |
| // restore s0 again from dscratch |
| abstract_cmd[3][63:32] = dm::csrr(dm::CSR_DSCRATCH0, 5'd8); |
| // GPR/FPR access |
| end else if (ac_ar.regno[12]) begin |
| // determine whether we want to access the floating point register or not |
| if (ac_ar.regno[5]) begin |
| abstract_cmd[2][31:0] = |
| dm::float_store(ac_ar.aarsize, ac_ar.regno[4:0], LoadBaseAddr, dm::DataAddr); |
| end else begin |
| abstract_cmd[2][31:0] = |
| dm::store(ac_ar.aarsize, ac_ar.regno[4:0], LoadBaseAddr, dm::DataAddr); |
| end |
| // CSR access |
| end else begin |
| // CSR register to data |
| // store s0 in dscratch |
| abstract_cmd[2][31:0] = dm::csrw(dm::CSR_DSCRATCH0, 5'd8); |
| // read value from CSR into s0 |
| abstract_cmd[2][63:32] = dm::csrr(dm::csr_reg_t'(ac_ar.regno[11:0]), 5'd8); |
| // and store s0 into data section |
| abstract_cmd[3][31:0] = dm::store(ac_ar.aarsize, 5'd8, LoadBaseAddr, dm::DataAddr); |
| // restore s0 again from dscratch |
| abstract_cmd[3][63:32] = dm::csrr(dm::CSR_DSCRATCH0, 5'd8); |
| end |
| end else if (32'(ac_ar.aarsize) >= MaxAar || ac_ar.aarpostincrement == 1'b1) begin |
| // this should happend when e.g. ac_ar.aarsize >= MaxAar |
| // Openocd will try to do an access with aarsize=64 bits |
| // first before falling back to 32 bits. |
| abstract_cmd[0][31:0] = dm::ebreak(); // we leave asap |
| unsupported_command = 1'b1; |
| end |
| |
| // Check whether we need to execute the program buffer. When we |
| // get an unsupported command we really should abort instead of |
| // still trying to execute the program buffer, makes it easier |
| // for the debugger to recover |
| if (ac_ar.postexec && !unsupported_command) begin |
| // issue a nop, we will automatically run into the program buffer |
| abstract_cmd[4][63:32] = dm::nop(); |
| end |
| end |
| // not supported at the moment |
| // dm::QuickAccess:; |
| // dm::AccessMemory:; |
| default: begin |
| abstract_cmd[0][31:0] = dm::ebreak(); |
| unsupported_command = 1'b1; |
| end |
| endcase |
| end |
| |
| logic [63:0] rom_addr; |
| assign rom_addr = 64'(addr_i); |
| |
| // Depending on whether the debug module is located |
| // at the zero page we can instantiate a simplified version |
| // which only requires one scratch register per hart. |
| // For all other cases we need to set aside |
| // two registers per hart, hence we also need |
| // two scratch registers. |
| if (HasSndScratch) begin : gen_rom_snd_scratch |
| debug_rom i_debug_rom ( |
| .clk_i, |
| .req_i, |
| .addr_i ( rom_addr ), |
| .rdata_o ( rom_rdata ) |
| ); |
| end else begin : gen_rom_one_scratch |
| // It uses the zero register (`x0`) as the base |
| // for its loads. The zero register does not need to |
| // be saved. |
| debug_rom_one_scratch i_debug_rom ( |
| .clk_i, |
| .req_i, |
| .addr_i ( rom_addr ), |
| .rdata_o ( rom_rdata ) |
| ); |
| end |
| |
| // ROM starts at the HaltAddress of the core e.g.: it immediately jumps to |
| // the ROM base address |
| assign fwd_rom_d = logic'(addr_i[DbgAddressBits-1:0] >= dm::HaltAddress[DbgAddressBits-1:0]); |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs |
| if (!rst_ni) begin |
| fwd_rom_q <= 1'b0; |
| rdata_q <= '0; |
| state_q <= Idle; |
| word_enable32_q <= 1'b0; |
| end else begin |
| fwd_rom_q <= fwd_rom_d; |
| rdata_q <= rdata_d; |
| state_q <= state_d; |
| word_enable32_q <= addr_i[2]; |
| end |
| end |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| halted_q <= 1'b0; |
| resuming_q <= 1'b0; |
| end else begin |
| halted_q <= SelectableHarts & halted_d; |
| resuming_q <= SelectableHarts & resuming_d; |
| end |
| end |
| |
| endmodule : dm_mem |