| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| // Top-level debug module (DM) |
| // |
| // This module implements the RISC-V debug specification version 0.13, |
| // |
| // This toplevel wraps the PULP debug module available from |
| // https://github.com/pulp-platform/riscv-dbg to match the needs of |
| // the TL-UL-based lowRISC chip design. |
| |
| `include "prim_assert.sv" |
| |
| module rv_dm |
| import rv_dm_reg_pkg::*; |
| #( |
| parameter logic [NumAlerts-1:0] AlertAsyncOn = {NumAlerts{1'b1}}, |
| parameter logic [31:0] IdcodeValue = 32'h 0000_0001 |
| ) ( |
| input logic clk_i, // clock |
| input logic rst_ni, // asynchronous reset active low, connect PoR |
| // here, not the system reset |
| // SEC_CM: LC_HW_DEBUG_EN.INTERSIG.MUBI |
| input lc_ctrl_pkg::lc_tx_t lc_hw_debug_en_i, // Debug module lifecycle enable/disable |
| input prim_mubi_pkg::mubi4_t scanmode_i, |
| input scan_rst_ni, |
| output logic ndmreset_req_o, // non-debug module reset |
| output logic dmactive_o, // debug module is active |
| output logic [NrHarts-1:0] debug_req_o, // async debug request |
| input logic [NrHarts-1:0] unavailable_i, // communicate whether the hart is unavailable |
| // (e.g.: power down) |
| |
| // bus device for comportable CSR access |
| input tlul_pkg::tl_h2d_t regs_tl_d_i, |
| output tlul_pkg::tl_d2h_t regs_tl_d_o, |
| |
| // bus device with debug memory, for an execution based technique |
| input tlul_pkg::tl_h2d_t rom_tl_d_i, |
| output tlul_pkg::tl_d2h_t rom_tl_d_o, |
| |
| // bus host, for system bus accesses |
| output tlul_pkg::tl_h2d_t sba_tl_h_o, |
| input tlul_pkg::tl_d2h_t sba_tl_h_i, |
| |
| // Alerts |
| input prim_alert_pkg::alert_rx_t [NumAlerts-1:0] alert_rx_i, |
| output prim_alert_pkg::alert_tx_t [NumAlerts-1:0] alert_tx_o, |
| |
| input jtag_pkg::jtag_req_t jtag_i, |
| output jtag_pkg::jtag_rsp_t jtag_o |
| ); |
| |
| `ASSERT_INIT(paramCheckNrHarts, NrHarts > 0) |
| |
| // Currently only 32 bit busses are supported by our TL-UL IP |
| localparam int BusWidth = 32; |
| // all harts have contiguous IDs |
| localparam logic [NrHarts-1:0] SelectableHarts = {NrHarts{1'b1}}; |
| |
| // CSR Nodes |
| tlul_pkg::tl_h2d_t rom_tl_win_h2d; |
| tlul_pkg::tl_d2h_t rom_tl_win_d2h; |
| rv_dm_reg_pkg::rv_dm_regs_reg2hw_t regs_reg2hw; |
| logic regs_intg_error, rom_intg_error; |
| |
| rv_dm_regs_reg_top u_reg_regs ( |
| .clk_i, |
| .rst_ni, |
| .tl_i (regs_tl_d_i ), |
| .tl_o (regs_tl_d_o ), |
| .reg2hw (regs_reg2hw ), |
| // SEC_CM: BUS.INTEGRITY |
| .intg_err_o(regs_intg_error), |
| .devmode_i (1'b1 ) |
| ); |
| |
| rv_dm_rom_reg_top u_reg_rom ( |
| .clk_i, |
| .rst_ni, |
| .tl_i (rom_tl_d_i ), |
| .tl_o (rom_tl_d_o ), |
| .tl_win_o (rom_tl_win_h2d), |
| .tl_win_i (rom_tl_win_d2h), |
| .intg_err_o(), |
| .devmode_i (1'b1 ) |
| ); |
| |
| // Alerts |
| logic [NumAlerts-1:0] alert_test, alerts; |
| |
| assign alerts[0] = regs_intg_error | rom_intg_error; |
| |
| assign alert_test = { |
| regs_reg2hw.alert_test.q & |
| regs_reg2hw.alert_test.qe |
| }; |
| |
| for (genvar i = 0; i < NumAlerts; i++) begin : gen_alert_tx |
| prim_alert_sender #( |
| .AsyncOn(AlertAsyncOn[i]), |
| .IsFatal(1'b1) |
| ) u_prim_alert_sender ( |
| .clk_i, |
| .rst_ni, |
| .alert_test_i ( alert_test[i] ), |
| .alert_req_i ( alerts[0] ), |
| .alert_ack_o ( ), |
| .alert_state_o ( ), |
| .alert_rx_i ( alert_rx_i[i] ), |
| .alert_tx_o ( alert_tx_o[i] ) |
| ); |
| end |
| |
| // debug enable gating |
| typedef enum logic [2:0] { |
| EnFetch, |
| EnRom, |
| EnSba, |
| EnDebugReq, |
| EnResetReq, |
| EnDmiReq, |
| EnLastPos |
| } rv_dm_en_e; |
| |
| lc_ctrl_pkg::lc_tx_t [EnLastPos-1:0] lc_hw_debug_en; |
| prim_lc_sync #( |
| .NumCopies(int'(EnLastPos)) |
| ) u_lc_en_sync ( |
| .clk_i, |
| .rst_ni, |
| .lc_en_i(lc_hw_debug_en_i), |
| .lc_en_o(lc_hw_debug_en) |
| ); |
| |
| |
| // Debug CSRs |
| dm::hartinfo_t [NrHarts-1:0] hartinfo; |
| logic [NrHarts-1:0] halted; |
| // logic [NrHarts-1:0] running; |
| logic [NrHarts-1:0] resumeack; |
| logic [NrHarts-1:0] haltreq; |
| logic [NrHarts-1:0] resumereq; |
| logic clear_resumeack; |
| logic cmd_valid; |
| dm::command_t cmd; |
| |
| logic cmderror_valid; |
| dm::cmderr_e cmderror; |
| logic cmdbusy; |
| logic [dm::ProgBufSize-1:0][31:0] progbuf; |
| logic [dm::DataCount-1:0][31:0] data_csrs_mem; |
| logic [dm::DataCount-1:0][31:0] data_mem_csrs; |
| logic data_valid; |
| logic [19:0] hartsel; |
| // System Bus Access Module |
| logic [BusWidth-1:0] sbaddress_csrs_sba; |
| logic [BusWidth-1:0] sbaddress_sba_csrs; |
| logic sbaddress_write_valid; |
| logic sbreadonaddr; |
| logic sbautoincrement; |
| logic [2:0] sbaccess; |
| logic sbreadondata; |
| logic [BusWidth-1:0] sbdata_write; |
| logic sbdata_read_valid; |
| logic sbdata_write_valid; |
| logic [BusWidth-1:0] sbdata_read; |
| logic sbdata_valid; |
| logic sbbusy; |
| logic sberror_valid; |
| logic [2:0] sberror; |
| |
| dm::dmi_req_t dmi_req; |
| dm::dmi_resp_t dmi_rsp; |
| logic dmi_req_valid, dmi_req_ready; |
| logic dmi_rsp_valid, dmi_rsp_ready; |
| logic dmi_rst_n; |
| logic testmode; |
| |
| // Decode multibit scanmode enable |
| assign testmode = prim_mubi_pkg::mubi4_test_true_strict(scanmode_i); |
| |
| // static debug hartinfo |
| localparam dm::hartinfo_t DebugHartInfo = '{ |
| zero1: '0, |
| nscratch: 2, // Debug module needs at least two scratch regs |
| zero0: 0, |
| dataaccess: 1'b1, // data registers are memory mapped in the debugger |
| datasize: dm::DataCount, |
| dataaddr: dm::DataAddr |
| }; |
| for (genvar i = 0; i < NrHarts; i++) begin : gen_dm_hart_ctrl |
| assign hartinfo[i] = DebugHartInfo; |
| end |
| |
| logic reset_req_en; |
| logic ndmreset_req; |
| // SEC_CM: DM_EN.CTRL.LC_GATED |
| assign reset_req_en = (lc_hw_debug_en[EnResetReq] == lc_ctrl_pkg::On); |
| assign ndmreset_req_o = ndmreset_req & reset_req_en; |
| |
| logic dmi_en; |
| // SEC_CM: DM_EN.CTRL.LC_GATED |
| assign dmi_en = (lc_hw_debug_en[EnDmiReq] == lc_ctrl_pkg::On); |
| |
| dm_csrs #( |
| .NrHarts(NrHarts), |
| .BusWidth(BusWidth), |
| .SelectableHarts(SelectableHarts) |
| ) i_dm_csrs ( |
| .clk_i ( clk_i ), |
| .rst_ni ( rst_ni ), |
| .testmode_i ( testmode ), |
| .dmi_rst_ni ( dmi_rst_n ), |
| .dmi_req_valid_i ( dmi_req_valid & dmi_en), |
| .dmi_req_ready_o ( dmi_req_ready ), |
| .dmi_req_i ( dmi_req ), |
| .dmi_resp_valid_o ( dmi_rsp_valid ), |
| .dmi_resp_ready_i ( dmi_rsp_ready & dmi_en), |
| .dmi_resp_o ( dmi_rsp ), |
| .ndmreset_o ( ndmreset_req ), |
| .dmactive_o ( dmactive_o ), |
| .hartsel_o ( hartsel ), |
| .hartinfo_i ( hartinfo ), |
| .halted_i ( halted ), |
| .unavailable_i, |
| .resumeack_i ( resumeack ), |
| .haltreq_o ( haltreq ), |
| .resumereq_o ( resumereq ), |
| .clear_resumeack_o ( clear_resumeack ), |
| .cmd_valid_o ( cmd_valid ), |
| .cmd_o ( cmd ), |
| .cmderror_valid_i ( cmderror_valid ), |
| .cmderror_i ( cmderror ), |
| .cmdbusy_i ( cmdbusy ), |
| .progbuf_o ( progbuf ), |
| .data_i ( data_mem_csrs ), |
| .data_valid_i ( data_valid ), |
| .data_o ( data_csrs_mem ), |
| .sbaddress_o ( sbaddress_csrs_sba ), |
| .sbaddress_i ( sbaddress_sba_csrs ), |
| .sbaddress_write_valid_o ( sbaddress_write_valid ), |
| .sbreadonaddr_o ( sbreadonaddr ), |
| .sbautoincrement_o ( sbautoincrement ), |
| .sbaccess_o ( sbaccess ), |
| .sbreadondata_o ( sbreadondata ), |
| .sbdata_o ( sbdata_write ), |
| .sbdata_read_valid_o ( sbdata_read_valid ), |
| .sbdata_write_valid_o ( sbdata_write_valid ), |
| .sbdata_i ( sbdata_read ), |
| .sbdata_valid_i ( sbdata_valid ), |
| .sbbusy_i ( sbbusy ), |
| .sberror_valid_i ( sberror_valid ), |
| .sberror_i ( sberror ) |
| ); |
| |
| logic host_req; |
| logic [BusWidth-1:0] host_add; |
| logic host_we; |
| logic [BusWidth-1:0] host_wdata; |
| logic [BusWidth/8-1:0] host_be; |
| logic host_gnt; |
| logic host_r_valid; |
| logic [BusWidth-1:0] host_r_rdata; |
| logic host_r_err; |
| |
| dm_sba #( |
| .BusWidth(BusWidth) |
| ) i_dm_sba ( |
| .clk_i ( clk_i ), |
| .rst_ni ( rst_ni ), |
| .master_req_o ( host_req ), |
| .master_add_o ( host_add ), |
| .master_we_o ( host_we ), |
| .master_wdata_o ( host_wdata ), |
| .master_be_o ( host_be ), |
| .master_gnt_i ( host_gnt ), |
| .master_r_valid_i ( host_r_valid ), |
| .master_r_rdata_i ( host_r_rdata ), |
| .dmactive_i ( dmactive_o ), |
| .sbaddress_i ( sbaddress_csrs_sba ), |
| .sbaddress_o ( sbaddress_sba_csrs ), |
| .sbaddress_write_valid_i ( sbaddress_write_valid ), |
| .sbreadonaddr_i ( sbreadonaddr ), |
| .sbautoincrement_i ( sbautoincrement ), |
| .sbaccess_i ( sbaccess ), |
| .sbreadondata_i ( sbreadondata ), |
| .sbdata_i ( sbdata_write ), |
| .sbdata_read_valid_i ( sbdata_read_valid ), |
| .sbdata_write_valid_i ( sbdata_write_valid ), |
| .sbdata_o ( sbdata_read ), |
| .sbdata_valid_o ( sbdata_valid ), |
| .sbbusy_o ( sbbusy ), |
| .sberror_valid_o ( sberror_valid ), |
| .sberror_o ( sberror ) |
| ); |
| |
| logic sba_en; |
| tlul_pkg::tl_h2d_t sba_tl_h_o_int; |
| tlul_pkg::tl_d2h_t sba_tl_h_i_int; |
| // SEC_CM: DM_EN.CTRL.LC_GATED |
| assign sba_en = (lc_hw_debug_en[EnSba] == lc_ctrl_pkg::On); |
| |
| always_comb begin |
| sba_tl_h_o = sba_tl_h_o_int; |
| sba_tl_h_i_int = sba_tl_h_i; |
| sba_tl_h_o.a_valid = sba_tl_h_o_int.a_valid & sba_en; |
| sba_tl_h_i_int.d_valid = sba_tl_h_i.d_valid & sba_en; |
| end |
| |
| tlul_adapter_host #( |
| .EnableDataIntgGen(1), |
| .MAX_REQS(1) |
| ) tl_adapter_host_sba ( |
| .clk_i, |
| .rst_ni, |
| .req_i (host_req), |
| .instr_type_i (prim_mubi_pkg::MuBi4False), |
| .gnt_o (host_gnt), |
| .addr_i (host_add), |
| .we_i (host_we), |
| .wdata_i (host_wdata), |
| .wdata_intg_i ('0), |
| .be_i (host_be), |
| .valid_o (host_r_valid), |
| .rdata_o (host_r_rdata), |
| .rdata_intg_o (), |
| .err_o (host_r_err), |
| .intg_err_o (), |
| .tl_o (sba_tl_h_o_int), |
| .tl_i (sba_tl_h_i_int) |
| ); |
| |
| // DBG doesn't handle error responses so raise assertion if we see one |
| `ASSERT(dbgNoErrorResponse, host_r_valid |-> !host_r_err) |
| |
| logic unused_host_r_err; |
| assign unused_host_r_err = host_r_err; |
| |
| localparam int unsigned AddressWidthWords = BusWidth - $clog2(BusWidth/8); |
| |
| logic req; |
| logic we; |
| logic [BusWidth/8-1:0] be; |
| logic [BusWidth-1:0] wdata; |
| logic [BusWidth-1:0] rdata; |
| logic rvalid; |
| |
| logic [BusWidth-1:0] addr_b; |
| logic [AddressWidthWords-1:0] addr_w; |
| |
| // TODO: The tlul_adapter_sram give us a bitwise write mask currently, |
| // but dm_mem only supports byte write masks. Disable sub-word access in the |
| // adapter for now until we figure out a good strategy to deal with this. |
| assign be = {BusWidth/8{1'b1}}; |
| |
| assign addr_b = {addr_w, {$clog2(BusWidth/8){1'b0}}}; |
| |
| logic debug_req_en; |
| logic debug_req; |
| // SEC_CM: DM_EN.CTRL.LC_GATED |
| assign debug_req_en = (lc_hw_debug_en[EnDebugReq] == lc_ctrl_pkg::On); |
| assign debug_req_o = debug_req & debug_req_en; |
| |
| |
| dm_mem #( |
| .NrHarts(NrHarts), |
| .BusWidth(BusWidth), |
| .SelectableHarts(SelectableHarts), |
| // The debug module provides a simplified ROM for systems that map the debug ROM to offset 0x0 |
| // on the system bus. In that case, only one scratch register has to be implemented in the core. |
| // However, we require that the DM can be placed at arbitrary offsets in the system, which |
| // requires the generalized debug ROM implementation and two scratch registers. We hence set |
| // this parameter to a non-zero value (inside dm_mem, this just feeds into a comparison with 0). |
| .DmBaseAddress(1) |
| ) i_dm_mem ( |
| .clk_i ( clk_i ), |
| .rst_ni ( rst_ni ), |
| .debug_req_o ( debug_req ), |
| .hartsel_i ( hartsel ), |
| .haltreq_i ( haltreq ), |
| .resumereq_i ( resumereq ), |
| .clear_resumeack_i ( clear_resumeack ), |
| .halted_o ( halted ), |
| .resuming_o ( resumeack ), |
| .cmd_valid_i ( cmd_valid ), |
| .cmd_i ( cmd ), |
| .cmderror_valid_o ( cmderror_valid ), |
| .cmderror_o ( cmderror ), |
| .cmdbusy_o ( cmdbusy ), |
| .progbuf_i ( progbuf ), |
| .data_i ( data_csrs_mem ), |
| .data_o ( data_mem_csrs ), |
| .data_valid_o ( data_valid ), |
| .req_i ( req ), |
| .we_i ( we ), |
| .addr_i ( addr_b ), |
| .wdata_i ( wdata ), |
| .be_i ( be ), |
| .rdata_o ( rdata ) |
| ); |
| |
| // Bound-in DPI module replaces the TAP |
| `ifndef DMIDirectTAP |
| |
| logic tck_muxed; |
| logic trst_n_muxed; |
| prim_clock_mux2 #( |
| .NoFpgaBufG(1'b1) |
| ) u_prim_clock_mux2 ( |
| .clk0_i(jtag_i.tck), |
| .clk1_i(clk_i), |
| .sel_i (testmode), |
| .clk_o (tck_muxed) |
| ); |
| |
| prim_clock_mux2 #( |
| .NoFpgaBufG(1'b1) |
| ) u_prim_rst_n_mux2 ( |
| .clk0_i(jtag_i.trst_n), |
| .clk1_i(scan_rst_ni), |
| .sel_i (testmode), |
| .clk_o (trst_n_muxed) |
| ); |
| |
| // JTAG TAP |
| dmi_jtag #( |
| .IdcodeValue (IdcodeValue) |
| ) dap ( |
| .clk_i (clk_i), |
| .rst_ni (rst_ni), |
| .testmode_i (testmode), |
| |
| .dmi_rst_no (dmi_rst_n), |
| .dmi_req_o (dmi_req), |
| .dmi_req_valid_o (dmi_req_valid), |
| .dmi_req_ready_i (dmi_req_ready & dmi_en), |
| |
| .dmi_resp_i (dmi_rsp ), |
| .dmi_resp_ready_o (dmi_rsp_ready), |
| .dmi_resp_valid_i (dmi_rsp_valid & dmi_en), |
| |
| //JTAG |
| .tck_i (tck_muxed), |
| .tms_i (jtag_i.tms), |
| .trst_ni (trst_n_muxed), |
| .td_i (jtag_i.tdi), |
| .td_o (jtag_o.tdo), |
| .tdo_oe_o (jtag_o.tdo_oe) |
| ); |
| `endif |
| |
| prim_mubi_pkg::mubi4_t en_ifetch; |
| // SEC_CM: DM_EN.CTRL.LC_GATED |
| assign en_ifetch = (lc_hw_debug_en[EnFetch] == lc_ctrl_pkg::On) ? |
| prim_mubi_pkg::MuBi4True : |
| prim_mubi_pkg::MuBi4False; |
| |
| logic rom_en; |
| // SEC_CM: DM_EN.CTRL.LC_GATED |
| assign rom_en = (lc_hw_debug_en[EnRom] == lc_ctrl_pkg::On); |
| |
| tlul_adapter_sram #( |
| .SramAw(AddressWidthWords), |
| .SramDw(BusWidth), |
| .Outstanding(1), |
| .ByteAccess(0), |
| .CmdIntgCheck(1), |
| .EnableRspIntgGen(1) |
| ) tl_adapter_device_mem ( |
| .clk_i, |
| .rst_ni, |
| .en_ifetch_i (en_ifetch), |
| .req_o (req), |
| .req_type_o (), |
| .gnt_i (rom_en), |
| .we_o (we), |
| .addr_o (addr_w), |
| .wdata_o (wdata), |
| .wmask_o (), |
| // SEC_CM: BUS.INTEGRITY |
| .intg_error_o(rom_intg_error), |
| .rdata_i (rdata & {BusWidth{rom_en}}), |
| .rvalid_i (rvalid & rom_en), |
| .rerror_i ({2{~rom_en}}), |
| |
| .tl_o (rom_tl_win_d2h), |
| .tl_i (rom_tl_win_h2d) |
| ); |
| |
| always_ff @(posedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| rvalid <= '0; |
| end else begin |
| rvalid <= req & ~we; |
| end |
| end |
| |
| `ASSERT_KNOWN(TlRegsDValidKnown_A, regs_tl_d_o.d_valid) |
| `ASSERT_KNOWN(TlRegsAReadyKnown_A, regs_tl_d_o.a_ready) |
| |
| `ASSERT_KNOWN(TlRomDValidKnown_A, rom_tl_d_o.d_valid) |
| `ASSERT_KNOWN(TlRomAReadyKnown_A, rom_tl_d_o.a_ready) |
| |
| `ASSERT_KNOWN(TlSbaAValidKnown_A, sba_tl_h_o.a_valid) |
| `ASSERT_KNOWN(TlSbaDReadyKnown_A, sba_tl_h_o.d_ready) |
| |
| `ASSERT_KNOWN(NdmresetOKnown_A, ndmreset_req_o) |
| `ASSERT_KNOWN(DmactiveOKnown_A, dmactive_o) |
| `ASSERT_KNOWN(DebugReqOKnown_A, debug_req_o) |
| |
| // JTAG TDO is driven by an inverted TCK in dmi_jtag_tap.sv |
| `ASSERT_KNOWN(JtagRspOTdoKnown_A, jtag_o.tdo, !jtag_i.tck, !jtag_i.trst_n) |
| `ASSERT_KNOWN(JtagRspOTdoOeKnown_A, jtag_o.tdo_oe, !jtag_i.tck, !jtag_i.trst_n) |
| |
| endmodule |