blob: e74d3207b21f6a9e13fd7f574f90953f4a31afdd [file] [log] [blame]
/* 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 unsigned NrHarts = 1,
parameter int unsigned BusWidth = 32,
parameter logic [NrHarts-1:0] SelectableHarts = {NrHarts{1'b1}}
) (
input logic clk_i, // Clock
input logic rst_ni, // Asynchronous reset active low
input logic testmode_i,
input logic dmi_rst_ni, // Debug Module Intf 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 clear_resumeack_o,
output logic cmd_valid_o, // debugger writing to cmd field
output dm::command_t cmd_o, // abstract command
input logic cmderror_valid_i, // an error occurred
input dm::cmderr_e cmderror_i, // this error occurred
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 [BusWidth-1:0] sbaddress_o,
input logic [BusWidth-1: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 [BusWidth-1:0] sbdata_o,
output logic sbdata_read_valid_o,
output logic sbdata_write_valid_o,
// read data in
input logic [BusWidth-1: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 int unsigned HartSelLen = (NrHarts == 1) ? 1 : $clog2(NrHarts);
localparam int unsigned NrHartsAligned = 2**HartSelLen;
dm::dtm_op_e dtm_op;
assign dtm_op = dm::dtm_op_e'(dmi_req_i.op);
logic [31:0] resp_queue_data;
localparam dm::dm_csr_e DataEnd = dm::dm_csr_e'(dm::Data0 + {4'b0, dm::DataCount} - 8'h1);
localparam dm::dm_csr_e ProgBufEnd = dm::dm_csr_e'(dm::ProgBuf0 + {4'b0, dm::ProgBufSize} - 8'h1);
logic [31:0] haltsum0, haltsum1, haltsum2, haltsum3;
logic [((NrHarts-1)/2**5 + 1) * 32 - 1 : 0] halted;
logic [(NrHarts-1)/2**5:0][31:0] halted_reshaped0;
logic [(NrHarts-1)/2**10:0][31:0] halted_reshaped1;
logic [(NrHarts-1)/2**15:0][31:0] halted_reshaped2;
logic [((NrHarts-1)/2**10+1)*32-1:0] halted_flat1;
logic [((NrHarts-1)/2**15+1)*32-1:0] halted_flat2;
logic [31:0] halted_flat3;
// haltsum0
logic [14:0] hartsel_idx0;
always_comb begin : p_haltsum0
halted = '0;
haltsum0 = '0;
hartsel_idx0 = hartsel_o[19:5];
halted[NrHarts-1:0] = halted_i;
halted_reshaped0 = halted;
if (hartsel_idx0 < 15'((NrHarts-1)/2**5+1)) begin
haltsum0 = halted_reshaped0[hartsel_idx0];
end
end
// haltsum1
logic [9:0] hartsel_idx1;
always_comb begin : p_reduction1
halted_flat1 = '0;
haltsum1 = '0;
hartsel_idx1 = hartsel_o[19:10];
for (int unsigned k = 0; k < (NrHarts-1)/2**5+1; k++) begin
halted_flat1[k] = |halted_reshaped0[k];
end
halted_reshaped1 = halted_flat1;
if (hartsel_idx1 < 10'(((NrHarts-1)/2**10+1))) begin
haltsum1 = halted_reshaped1[hartsel_idx1];
end
end
// haltsum2
logic [4:0] hartsel_idx2;
always_comb begin : p_reduction2
halted_flat2 = '0;
haltsum2 = '0;
hartsel_idx2 = hartsel_o[19:15];
for (int unsigned k = 0; k < (NrHarts-1)/2**10+1; k++) begin
halted_flat2[k] = |halted_reshaped1[k];
end
halted_reshaped2 = halted_flat2;
if (hartsel_idx2 < 5'(((NrHarts-1)/2**15+1))) begin
haltsum2 = halted_reshaped2[hartsel_idx2];
end
end
// haltsum3
always_comb begin : p_reduction3
halted_flat3 = '0;
for (int unsigned k = 0; k < NrHarts/2**15+1; 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_e cmderr_d, cmderr_q;
dm::command_t command_d, command_q;
logic cmd_valid_d, cmd_valid_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;
logic [dm::DataCount-1:0][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;
// 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[BusWidth-1:0];
assign sbaddress_o = sbaddr_q[BusWidth-1:0];
assign hartsel_o = {dmcontrol_q.hartselhi, dmcontrol_q.hartsello};
// needed to avoid lint warnings
logic [NrHartsAligned-1:0] havereset_d_aligned, havereset_q_aligned,
resumeack_aligned, unavailable_aligned,
halted_aligned;
assign resumeack_aligned = NrHartsAligned'(resumeack_i);
assign unavailable_aligned = NrHartsAligned'(unavailable_i);
assign halted_aligned = NrHartsAligned'(halted_i);
assign havereset_d = NrHarts'(havereset_d_aligned);
assign havereset_q_aligned = NrHartsAligned'(havereset_q);
dm::hartinfo_t [NrHartsAligned-1:0] hartinfo_aligned;
always_comb begin : p_hartinfo_align
hartinfo_aligned = '0;
hartinfo_aligned[NrHarts-1:0] = hartinfo_i;
end
// helper variables
dm::sbcs_t sbcs;
dm::dmcontrol_t dmcontrol;
dm::abstractcs_t a_abstractcs;
logic [4:0] autoexecdata_idx;
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_aligned[selected_hart];
dmstatus.anyhavereset = havereset_q_aligned[selected_hart];
dmstatus.allresumeack = resumeack_aligned[selected_hart];
dmstatus.anyresumeack = resumeack_aligned[selected_hart];
dmstatus.allunavail = unavailable_aligned[selected_hart];
dmstatus.anyunavail = unavailable_aligned[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 = logic'(32'(hartsel_o) > (NrHarts - 1));
dmstatus.anynonexistent = logic'(32'(hartsel_o) > (NrHarts - 1));
// We are not allowed to be in multiple states at once. This is a to
// make the running/halted and unavailable states exclusive.
dmstatus.allhalted = halted_aligned[selected_hart] & ~unavailable_aligned[selected_hart];
dmstatus.anyhalted = halted_aligned[selected_hart] & ~unavailable_aligned[selected_hart];
dmstatus.allrunning = ~halted_aligned[selected_hart] & ~unavailable_aligned[selected_hart];
dmstatus.anyrunning = ~halted_aligned[selected_hart] & ~unavailable_aligned[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_aligned = NrHartsAligned'(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 = 64'(sbaddress_i);
sbdata_d = sbdata_q;
resp_queue_data = 32'b0;
cmd_valid_d = 1'b0;
sbaddress_write_valid_o = 1'b0;
sbdata_read_valid_o = 1'b0;
sbdata_write_valid_o = 1'b0;
clear_resumeack_o = 1'b0;
// helper variables
sbcs = '0;
dmcontrol = '0;
a_abstractcs = '0;
autoexecdata_idx = dmi_req_i.addr[4:0] - 5'(dm::Data0);
// localparam int unsigned DataCountAlign = $clog2(dm::DataCount);
// 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
// logic [$clog2(dm::DataCount)-1:0] resp_queue_idx;
// resp_queue_idx = dmi_req_i.addr[4:0] - int'(dm::Data0);
resp_queue_data = data_q[$clog2(dm::DataCount)'(autoexecdata_idx)];
if (!cmdbusy_i) begin
// check whether we need to re-execute the command (just give a cmd_valid)
if (autoexecdata_idx < $bits(abstractauto_q.autoexecdata)) begin
cmd_valid_d = abstractauto_q.autoexecdata[autoexecdata_idx];
end
end
end
dm::DMControl: resp_queue_data = dmcontrol_q;
dm::DMStatus: resp_queue_data = dmstatus;
dm::Hartinfo: resp_queue_data = hartinfo_aligned[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[$clog2(dm::ProgBufSize)-1:0]];
if (!cmdbusy_i) begin
// check whether we need to re-execute the command (just give a cmd_valid)
// range of autoexecprogbuf is 31:16
cmd_valid_d = abstractauto_q.autoexecprogbuf[{1'b1, dmi_req_i.addr[3:0]}];
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
resp_queue_data = sbcs_q;
end
dm::SBAddress0: begin
// access while the SBA was busy
if (sbbusy_i) begin
sbcs_d.sbbusyerror = 1'b1;
end else 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 else 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 else 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 else 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_e'({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[$clog2(dm::DataCount)-1:0]] = dmi_req_i.data;
// check whether we need to re-execute the command (just give a cmd_valid)
if (autoexecdata_idx < $bits(abstractauto_q.autoexecdata)) begin
cmd_valid_d = abstractauto_q.autoexecdata[autoexecdata_idx];
end
end
end
dm::DMControl: begin
dmcontrol = dm::dmcontrol_t'(dmi_req_i.data);
// clear the havreset of the selected hart
if (dmcontrol.ackhavereset) begin
havereset_d_aligned[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.
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_e'(~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_d = 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 = 32'b0;
abstractauto_d.autoexecdata = 12'(dmi_req_i.data[dm::DataCount-1:0]);
abstractauto_d.autoexecprogbuf = 16'(dmi_req_i.data[dm::ProgBufSize-1+16:16]);
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[$clog2(dm::ProgBufSize)-1: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
// range of autoexecprogbuf is 31:16
cmd_valid_d = abstractauto_q.autoexecprogbuf[{1'b1, dmi_req_i.addr[3:0]}];
end
end
dm::SBCS: begin
// access while the SBA was busy
if (sbbusy_i) begin
sbcs_d.sbbusyerror = 1'b1;
end else begin
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 else 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 else 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 else 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 else 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) begin
data_d = data_i;
end
// set the havereset flag when we did a ndmreset
if (ndmreset_o) begin
havereset_d_aligned[NrHarts-1:0] = '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 = 64'(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;
if (!dmcontrol_q.resumereq && dmcontrol_d.resumereq) begin
clear_resumeack_o = 1'b1;
end
if (dmcontrol_q.resumereq && resumeack_i) begin
dmcontrol_d.resumereq = 1'b0;
end
// static values for dcsr
sbcs_d.sbversion = 3'd1;
sbcs_d.sbbusy = sbbusy_i;
sbcs_d.sbasize = $bits(sbcs_d.sbasize)'(BusWidth);
sbcs_d.sbaccess128 = 1'b0;
sbcs_d.sbaccess64 = logic'(BusWidth == 32'd64);
sbcs_d.sbaccess32 = logic'(BusWidth == 32'd32);
sbcs_d.sbaccess16 = 1'b0;
sbcs_d.sbaccess8 = 1'b0;
sbcs_d.sbaccess = (BusWidth == 32'd64) ? 3'd3 : 3'd2;
end
// output multiplexer
always_comb begin : p_outmux
selected_hart = hartsel_o[HartSelLen-1:0];
// default assignment
haltreq_o = '0;
resumereq_o = '0;
if (selected_hart < (HartSelLen+1)'(NrHarts)) begin
haltreq_o[selected_hart] = dmcontrol_q.haltreq;
resumereq_o[selected_hart] = dmcontrol_q.resumereq;
end
end
assign dmactive_o = dmcontrol_q.dmactive;
assign cmd_o = command_q;
assign cmd_valid_o = cmd_valid_q;
assign progbuf_o = progbuf_q;
assign data_o = data_q;
assign ndmreset_o = dmcontrol_q.ndmreset;
logic unused_testmode;
assign unused_testmode = testmode_i;
// response FIFO
fifo_sync #(
.Width (32),
.Pass (1'b0),
.Depth (2)
) i_fifo (
.clk_i ( clk_i ),
.rst_ni ( dmi_rst_ni ), // reset only when system is re-set
.clr_i ( 1'b0 ),
.wdata_i ( resp_queue_data ),
.wvalid_i( dmi_req_valid_i ),
.wready_o( dmi_req_ready_o ),
.rdata_o ( dmi_resp_o.data ),
.rvalid_o( dmi_resp_valid_o ),
.rready_i( dmi_resp_ready_i ),
.depth_o ( ) // Doesn't use
);
always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
// PoR
if (!rst_ni) begin
dmcontrol_q <= '0;
// this is the only write-able bit during reset
cmderr_q <= dm::CmdErrNone;
command_q <= '0;
cmd_valid_q <= '0;
abstractauto_q <= '0;
progbuf_q <= '0;
data_q <= '0;
sbcs_q <= '0;
sbaddr_q <= '0;
sbdata_q <= '0;
havereset_q <= '1;
end else begin
havereset_q <= SelectableHarts & 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.ackhavereset <= '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;
cmd_valid_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;
cmd_valid_q <= cmd_valid_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
//pragma translate_on
endmodule : dm_csrs