/* 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_csrs.sv * Author: Florian Zaruba <zarubaf@iis.ee.ethz.ch> * Date: 30.6.2018 * * Description: Debug CSRs. Communication over Debug Transport Module (DTM) */ module dm_csrs #( parameter int NrHarts = -1 ) ( input logic clk_i, // Clock input logic rst_ni, // Asynchronous reset active low input logic testmode_i, input logic dmi_rst_ni, // Debug Module Interface reset, active-low input logic dmi_req_valid_i, output logic dmi_req_ready_o, input dm::dmi_req_t dmi_req_i, // every request needs a response one cycle later output logic dmi_resp_valid_o, input logic dmi_resp_ready_i, output dm::dmi_resp_t dmi_resp_o, // global ctrl output logic ndmreset_o, // non-debug module reset, active-high output logic dmactive_o, // 1 -> debug-module is active, 0 -> synchronous re-set // hart status input dm::hartinfo_t [NrHarts-1:0] hartinfo_i, // static hartinfo input logic [NrHarts-1:0] halted_i, // hart is halted input logic [NrHarts-1:0] unavailable_i, // e.g.: powered down input logic [NrHarts-1:0] resumeack_i, // hart acknowledged resume request // hart control output logic [19:0] hartsel_o, // hartselect to ctrl module output logic [NrHarts-1:0] haltreq_o, // request to halt a hart output logic [NrHarts-1:0] resumereq_o, // request hart to resume output logic cmd_valid_o, // debugger is writing to the command field output dm::command_t cmd_o, // abstract command input logic cmderror_valid_i, // an error occured input dm::cmderr_t cmderror_i, // this error occured input logic cmdbusy_i, // cmd is currently busy executing output logic [dm::ProgBufSize-1:0][31:0] progbuf_o, // to system bus output logic [dm::DataCount-1:0][31:0] data_o, input logic [dm::DataCount-1:0][31:0] data_i, input logic data_valid_i, // system bus access module (SBA) output logic [63:0] sbaddress_o, input logic [63:0] sbaddress_i, output logic sbaddress_write_valid_o, // control signals in output logic sbreadonaddr_o, output logic sbautoincrement_o, output logic [2:0] sbaccess_o, // data out output logic sbreadondata_o, output logic [63:0] sbdata_o, output logic sbdata_read_valid_o, output logic sbdata_write_valid_o, // read data in input logic [63:0] sbdata_i, input logic sbdata_valid_i, // control signals input logic sbbusy_i, input logic sberror_valid_i, // bus error occurred input logic [2:0] sberror_i // bus error occurred ); // the amount of bits we need to represent all harts localparam HartSelLen = (NrHarts == 1) ? 1 : $clog2(NrHarts); dm::dtm_op_t dtm_op; assign dtm_op = dm::dtm_op_t'(dmi_req_i.op); logic resp_queue_full; logic resp_queue_empty; logic resp_queue_push; logic resp_queue_pop; logic [31:0] resp_queue_data; localparam dm::dm_csr_t DataEnd = dm::dm_csr_t'((dm::Data0 + {4'b0, dm::DataCount})); localparam dm::dm_csr_t ProgBufEnd = dm::dm_csr_t'((dm::ProgBuf0 + {4'b0, dm::ProgBufSize})); logic [31:0] haltsum0, haltsum1, haltsum2, haltsum3; logic [NrHarts/2**5 :0][31:0] halted_reshaped0; logic [NrHarts/2**10:0][31:0] halted_reshaped1; logic [NrHarts/2**15:0][31:0] halted_reshaped2; logic [(NrHarts/2**10+1)*32-1:0] halted_flat1; logic [(NrHarts/2**15+1)*32-1:0] halted_flat2; logic [32-1:0] halted_flat3; // haltsum0 assign halted_reshaped0 = halted_i; assign haltsum0 = halted_reshaped0[hartsel_o[19:5]]; // haltsum1 always_comb begin : p_reduction1 halted_flat1 = '0; for (int k=0; k<NrHarts/2**5; k++) begin halted_flat1[k] = &halted_reshaped0[k]; end halted_reshaped1 = halted_flat1; haltsum1 = halted_reshaped1[hartsel_o[19:10]]; end // haltsum2 always_comb begin : p_reduction2 halted_flat2 = '0; for (int k=0; k<NrHarts/2**10; k++) begin halted_flat2[k] = &halted_reshaped1[k]; end halted_reshaped2 = halted_flat2; haltsum2 = halted_reshaped2[hartsel_o[19:15]]; end // haltsum3 always_comb begin : p_reduction3 halted_flat3 = '0; for (int k=0; k<NrHarts/2**15; k++) begin halted_flat3[k] = &halted_reshaped2[k]; end haltsum3 = halted_flat3; end dm::dmstatus_t dmstatus; dm::dmcontrol_t dmcontrol_d, dmcontrol_q; dm::abstractcs_t abstractcs; dm::cmderr_t cmderr_d, cmderr_q; dm::command_t command_d, command_q; dm::abstractauto_t abstractauto_d, abstractauto_q; dm::sbcs_t sbcs_d, sbcs_q; logic [63:0] sbaddr_d, sbaddr_q; logic [63:0] sbdata_d, sbdata_q; logic [NrHarts-1:0] havereset_d, havereset_q; // program buffer logic [dm::ProgBufSize-1:0][31:0] progbuf_d, progbuf_q; // because first data address starts at 0x04 logic [({3'b0, dm::DataCount} + dm::Data0 - 1):(dm::Data0)][31:0] data_d, data_q; logic [HartSelLen-1:0] selected_hart; // a successful response returns zero assign dmi_resp_o.resp = dm::DTM_SUCCESS; assign dmi_resp_valid_o = ~resp_queue_empty; assign dmi_req_ready_o = ~resp_queue_full; assign resp_queue_push = dmi_req_valid_i & dmi_req_ready_o; // SBA assign sbautoincrement_o = sbcs_q.sbautoincrement; assign sbreadonaddr_o = sbcs_q.sbreadonaddr; assign sbreadondata_o = sbcs_q.sbreadondata; assign sbaccess_o = sbcs_q.sbaccess; assign sbdata_o = sbdata_q; assign sbaddress_o = sbaddr_q; assign hartsel_o = {dmcontrol_q.hartselhi, dmcontrol_q.hartsello}; always_comb begin : csr_read_write // -------------------- // Static Values (R/O) // -------------------- // dmstatus dmstatus = '0; dmstatus.version = dm::DbgVersion013; // no authentication implemented dmstatus.authenticated = 1'b1; // we do not support halt-on-reset sequence dmstatus.hasresethaltreq = 1'b0; // TODO(zarubaf) things need to change here if we implement the array mask dmstatus.allhavereset = havereset_q[selected_hart]; dmstatus.anyhavereset = havereset_q[selected_hart]; dmstatus.allresumeack = resumeack_i[selected_hart]; dmstatus.anyresumeack = resumeack_i[selected_hart]; dmstatus.allunavail = unavailable_i[selected_hart]; dmstatus.anyunavail = unavailable_i[selected_hart]; // as soon as we are out of the legal Hart region tell the debugger // that there are only non-existent harts dmstatus.allnonexistent = (hartsel_o > NrHarts[19:0] - 1) ? 1'b1 : 1'b0; dmstatus.anynonexistent = (hartsel_o > NrHarts[19:0] - 1) ? 1'b1 : 1'b0; dmstatus.allhalted = halted_i[selected_hart]; dmstatus.anyhalted = halted_i[selected_hart]; dmstatus.allrunning = ~halted_i[selected_hart]; dmstatus.anyrunning = ~halted_i[selected_hart]; // abstractcs abstractcs = '0; abstractcs.datacount = dm::DataCount; abstractcs.progbufsize = dm::ProgBufSize; abstractcs.busy = cmdbusy_i; abstractcs.cmderr = cmderr_q; // abstractautoexec abstractauto_d = abstractauto_q; abstractauto_d.zero0 = '0; // default assignments havereset_d = havereset_q; dmcontrol_d = dmcontrol_q; cmderr_d = cmderr_q; command_d = command_q; progbuf_d = progbuf_q; data_d = data_q; sbcs_d = sbcs_q; sbaddr_d = sbaddress_i; sbdata_d = sbdata_q; resp_queue_data = 32'b0; cmd_valid_o = 1'b0; sbaddress_write_valid_o = 1'b0; sbdata_read_valid_o = 1'b0; sbdata_write_valid_o = 1'b0; // reads if (dmi_req_ready_o && dmi_req_valid_i && dtm_op == dm::DTM_READ) begin unique case ({1'b0, dmi_req_i.addr}) inside [(dm::Data0):DataEnd]: begin if (dm::DataCount > 0) begin resp_queue_data = data_q[dmi_req_i.addr[4:0]]; end if (!cmdbusy_i) begin // check whether we need to re-execute the command (just give a cmd_valid) cmd_valid_o = abstractauto_q.autoexecdata[dmi_req_i.addr[3:0] - int'(dm::Data0)]; end end dm::DMControl: resp_queue_data = dmcontrol_q; dm::DMStatus: resp_queue_data = dmstatus; dm::Hartinfo: resp_queue_data = hartinfo_i[selected_hart]; dm::AbstractCS: resp_queue_data = abstractcs; dm::AbstractAuto: resp_queue_data = abstractauto_q; // command is read-only dm::Command: resp_queue_data = '0; [(dm::ProgBuf0):ProgBufEnd]: begin resp_queue_data = progbuf_q[dmi_req_i.addr[4:0]]; if (!cmdbusy_i) begin // check whether we need to re-execute the command (just give a cmd_valid) // TODO(zarubaf): check if offset is correct - without it this may assign Xes cmd_valid_o = abstractauto_q.autoexecprogbuf[dmi_req_i.addr[3:0]+16]; end end dm::HaltSum0: resp_queue_data = haltsum0; dm::HaltSum1: resp_queue_data = haltsum1; dm::HaltSum2: resp_queue_data = haltsum2; dm::HaltSum3: resp_queue_data = haltsum3; dm::SBCS: begin if (sbbusy_i) begin sbcs_d.sbbusyerror = 1'b1; end end dm::SBAddress0: begin // access while the SBA was busy if (sbbusy_i) begin sbcs_d.sbbusyerror = 1'b1; end begin resp_queue_data = sbaddr_q[31:0]; end end dm::SBAddress1: begin // access while the SBA was busy if (sbbusy_i) begin sbcs_d.sbbusyerror = 1'b1; end begin resp_queue_data = sbaddr_q[63:32]; end end dm::SBData0: begin // access while the SBA was busy if (sbbusy_i) begin sbcs_d.sbbusyerror = 1'b1; end begin sbdata_read_valid_o = (sbcs_q.sberror == '0); resp_queue_data = sbdata_q[31:0]; end end dm::SBData1: begin // access while the SBA was busy if (sbbusy_i) begin sbcs_d.sbbusyerror = 1'b1; end begin resp_queue_data = sbdata_q[63:32]; end end default:; endcase end // write if (dmi_req_ready_o && dmi_req_valid_i && dtm_op == dm::DTM_WRITE) begin unique case (dm::dm_csr_t'({1'b0, dmi_req_i.addr})) inside [(dm::Data0):DataEnd]: begin // attempts to write them while busy is set does not change their value if (!cmdbusy_i && dm::DataCount > 0) begin data_d[dmi_req_i.addr[4:0]] = dmi_req_i.data; // check whether we need to re-execute the command (just give a cmd_valid) cmd_valid_o = abstractauto_q.autoexecdata[dmi_req_i.addr[3:0] - int'(dm::Data0)]; end end dm::DMControl: begin automatic dm::dmcontrol_t dmcontrol; dmcontrol = dm::dmcontrol_t'(dmi_req_i.data); // clear the havreset of the selected hart if (dmcontrol.ackhavereset) begin havereset_d[selected_hart] = 1'b0; end dmcontrol_d = dmi_req_i.data; end dm::DMStatus:; // write are ignored to R/O register dm::Hartinfo:; // hartinfo is R/O // only command error is write-able dm::AbstractCS: begin // W1C // Gets set if an abstract command fails. The bits in this // field remain set until they are cleared by writing 1 to // them. No abstract command is started until the value is // reset to 0. automatic dm::abstractcs_t a_abstractcs; a_abstractcs = dm::abstractcs_t'(dmi_req_i.data); // reads during abstract command execution are not allowed if (!cmdbusy_i) begin cmderr_d = dm::cmderr_t'(~a_abstractcs.cmderr & cmderr_q); end else if (cmderr_q == dm::CmdErrNone) begin cmderr_d = dm::CmdErrBusy; end end dm::Command: begin // writes are ignored if a command is already busy if (!cmdbusy_i) begin cmd_valid_o = 1'b1; command_d = dm::command_t'(dmi_req_i.data); // if there was an attempted to write during a busy execution // and the cmderror field is zero set the busy error end else if (cmderr_q == dm::CmdErrNone) begin cmderr_d = dm::CmdErrBusy; end end dm::AbstractAuto: begin // this field can only be written legally when there is no command executing if (!cmdbusy_i) begin abstractauto_d = {dmi_req_i.data[31:16], 4'b0, dmi_req_i.data[11:0]}; end else if (cmderr_q == dm::CmdErrNone) begin cmderr_d = dm::CmdErrBusy; end end [(dm::ProgBuf0):ProgBufEnd]: begin // attempts to write them while busy is set does not change their value if (!cmdbusy_i) begin progbuf_d[dmi_req_i.addr[4:0]] = dmi_req_i.data; // check whether we need to re-execute the command (just give a cmd_valid) // this should probably throw an error if executed during another command was busy // TODO(zarubaf): check if offset is correct - without it this may assign Xes cmd_valid_o = abstractauto_q.autoexecprogbuf[dmi_req_i.addr[3:0]+16]; end end dm::SBCS: begin // access while the SBA was busy if (sbbusy_i) begin sbcs_d.sbbusyerror = 1'b1; end begin automatic dm::sbcs_t sbcs = dm::sbcs_t'(dmi_req_i.data); sbcs_d = sbcs; // R/W1C sbcs_d.sbbusyerror = sbcs_q.sbbusyerror & (~sbcs.sbbusyerror); sbcs_d.sberror = sbcs_q.sberror & (~sbcs.sberror); end end dm::SBAddress0: begin // access while the SBA was busy if (sbbusy_i) begin sbcs_d.sbbusyerror = 1'b1; end begin sbaddr_d[31:0] = dmi_req_i.data; sbaddress_write_valid_o = (sbcs_q.sberror == '0); end end dm::SBAddress1: begin // access while the SBA was busy if (sbbusy_i) begin sbcs_d.sbbusyerror = 1'b1; end begin sbaddr_d[63:32] = dmi_req_i.data; end end dm::SBData0: begin // access while the SBA was busy if (sbbusy_i) begin sbcs_d.sbbusyerror = 1'b1; end begin sbdata_d[31:0] = dmi_req_i.data; sbdata_write_valid_o = (sbcs_q.sberror == '0); end end dm::SBData1: begin // access while the SBA was busy if (sbbusy_i) begin sbcs_d.sbbusyerror = 1'b1; end begin sbdata_d[63:32] = dmi_req_i.data; end end default:; endcase end // hart threw a command error and has precedence over bus writes if (cmderror_valid_i) begin cmderr_d = cmderror_i; end // update data registers if (data_valid_i) data_d = data_i; // set the havereset flag when we did a ndmreset if (ndmreset_o) begin havereset_d = '1; end // ------------- // System Bus // ------------- // set bus error if (sberror_valid_i) begin sbcs_d.sberror = sberror_i; end // update read data if (sbdata_valid_i) begin sbdata_d = sbdata_i; end // dmcontrol // TODO(zarubaf) we currently do not implement the hartarry mask dmcontrol_d.hasel = 1'b0; // we do not support resetting an individual hart dmcontrol_d.hartreset = 1'b0; dmcontrol_d.setresethaltreq = 1'b0; dmcontrol_d.clrresethaltreq = 1'b0; dmcontrol_d.zero1 = '0; dmcontrol_d.zero0 = '0; // Non-writeable, clear only dmcontrol_d.ackhavereset = 1'b0; // static values for dcsr sbcs_d.sbversion = 3'b1; sbcs_d.sbbusy = sbbusy_i; sbcs_d.sbasize = 7'd64; // bus is 64 bit wide sbcs_d.sbaccess128 = 1'b0; sbcs_d.sbaccess64 = 1'b0; sbcs_d.sbaccess32 = 1'b0; sbcs_d.sbaccess16 = 1'b0; sbcs_d.sbaccess8 = 1'b0; sbcs_d.sbaccess = 1'b0; end // output multiplexer always_comb begin selected_hart = hartsel_o[HartSelLen-1:0]; // default assignment haltreq_o = '0; resumereq_o = '0; haltreq_o[selected_hart] = dmcontrol_q.haltreq; resumereq_o[selected_hart] = dmcontrol_q.resumereq; end assign dmactive_o = dmcontrol_q.dmactive; assign cmd_o = command_q; assign progbuf_o = progbuf_q; assign data_o = data_q; assign resp_queue_pop = dmi_resp_ready_i & ~resp_queue_empty; logic ndmreset_n; assign ndmreset_o = dmcontrol_q.ndmreset; // response FIFO fifo_v2 #( .dtype ( logic [31:0] ), .DEPTH ( 2 ) ) i_fifo ( .clk_i ( clk_i ), .rst_ni ( dmi_rst_ni ), // reset only when system is re-set .flush_i ( 1'b0 ), // we do not need to flush this queue .testmode_i ( testmode_i ), .full_o ( resp_queue_full ), .empty_o ( resp_queue_empty ), .alm_full_o ( ), .alm_empty_o ( ), .data_i ( resp_queue_data ), .push_i ( resp_queue_push ), .data_o ( dmi_resp_o.data ), .pop_i ( resp_queue_pop ) ); always_ff @(posedge clk_i or negedge rst_ni) begin // PoR if (~rst_ni) begin dmcontrol_q <= '0; havereset_q <= '1; // this is the only write-able bit during reset cmderr_q <= dm::CmdErrNone; command_q <= '0; abstractauto_q <= '0; progbuf_q <= '0; data_q <= '0; sbcs_q <= '0; sbaddr_q <= '0; sbdata_q <= '0; end else begin havereset_q <= havereset_d; // synchronous re-set of debug module, active-low, except for dmactive if (!dmcontrol_q.dmactive) begin dmcontrol_q.haltreq <= '0; dmcontrol_q.resumereq <= '0; dmcontrol_q.hartreset <= '0; dmcontrol_q.zero1 <= '0; dmcontrol_q.hasel <= '0; dmcontrol_q.hartsello <= '0; dmcontrol_q.hartselhi <= '0; dmcontrol_q.zero0 <= '0; dmcontrol_q.setresethaltreq <= '0; dmcontrol_q.clrresethaltreq <= '0; dmcontrol_q.ndmreset <= '0; // this is the only write-able bit during reset dmcontrol_q.dmactive <= dmcontrol_d.dmactive; cmderr_q <= dm::CmdErrNone; command_q <= '0; abstractauto_q <= '0; progbuf_q <= '0; data_q <= '0; sbcs_q <= '0; sbaddr_q <= '0; sbdata_q <= '0; end else begin dmcontrol_q <= dmcontrol_d; cmderr_q <= cmderr_d; command_q <= command_d; abstractauto_q <= abstractauto_d; progbuf_q <= progbuf_d; data_q <= data_d; sbcs_q <= sbcs_d; sbaddr_q <= sbaddr_d; sbdata_q <= sbdata_d; end end end /////////////////////////////////////////////////////// // assertions /////////////////////////////////////////////////////// //pragma translate_off `ifndef VERILATOR haltsum: assert property ( @(posedge clk_i) disable iff (~rst_ni) (dmi_req_ready_o && dmi_req_valid_i && dtm_op == dm::DTM_READ) |-> !({1'b0, dmi_req_i.addr} inside {dm::HaltSum0, dm::HaltSum1, dm::HaltSum2, dm::HaltSum3})) else $warning("Haltsums are not implemented yet and always return 0."); `endif //pragma translate_on endmodule