convert microwatt core_debug.vhdl to nmigen
[soc.git] / src / soc / debug / dmi.py
diff --git a/src/soc/debug/dmi.py b/src/soc/debug/dmi.py
new file mode 100644 (file)
index 0000000..102afab
--- /dev/null
@@ -0,0 +1,335 @@
+"""Converted from microwatt core_debug.vhdl to nmigen
+
+Provides a DMI (Debug Module Interface) for accessing a Libre-SOC core,
+compatible with microwatt's same interface.
+
+See constants below for addresses and register formats
+"""
+
+from nmigen import Elaboratable, Module, Signal, Cat, Const, Record, Array, Mux
+from nmutil.iocontrol import RecordObject
+from nmigen.utils import log2_int
+from nmigen.cli import rtlil
+
+
+# DMI register addresses
+class DBGCore:
+    CTRL           = 0b0000
+    STAT           = 0b0001
+    NIA            = 0b0010 # NIA register (read only for now)
+    MSR            = 0b0011 # MSR (read only)
+    GSPR_INDEX     = 0b0100 # GSPR register index
+    GSPR_DATA      = 0b0101 # GSPR register data
+    LOG_ADDR       = 0b0110 # Log buffer address register
+    LOG_DATA       = 0b0111 # Log buffer data register
+
+
+# CTRL register (direct actions, write 1 to act, read back 0)
+# bit     0 : Core stop
+# bit     1 : Core reset (doesn't clear stop)
+# bit     2 : Icache reset
+# bit     3 : Single step
+# bit     4 : Core start
+class DBGCtrl:
+    STOP    = 0
+    RESET   = 1
+    ICRESET = 2
+    STEP    = 3
+    START   = 4
+
+
+# STAT register (read only)
+# bit    0 : Core stopping (wait til bit 1 set)
+# bit    1 : Core stopped
+# bit    2 : Core terminated (clears with start or reset)
+class DBGStat:
+    STOPPING  = 0
+    STOPPED   = 1
+    TERM      = 2
+
+
+class DMIInterface(RecordObject):
+    def __init__(self, name):
+        super().__init__(name=name)
+        self.addr_i = Signal(4)   # DMI register address
+        self.din    = Signal(64)  # DMI data in (if we=1)
+        self.dout   = Signal(64)  # DMI data out (if we=0)
+        self.req_i  = Signal()    # DMI request valid (stb)
+        self.we_i   = Signal()    # DMI write-enable
+        self.ack_o  = Signal()    # DMI ack request
+
+
+class CoreDebug(Elaboratable):
+    def __init__(self, LOG_LENGTH=0): # TODO - debug log 512):
+        # Length of log buffer
+        self.LOG_LENGTH = LOG_LENGTH
+        self.dmi = DMIInterface("dmi")
+
+        # Debug actions
+        self.core_stop_o       = Signal()
+        self.core_rst_o        = Signal()
+        self.icache_rst_o      = Signal()
+
+        # Core status inputs
+        self.terminate_i    = Signal()
+        self.core_stopped_i = Signal()
+        self.nia            = Signal(64)
+        self.msr            = Signal(64)
+
+        # GSPR register read port
+        self.dbg_gpr_req_o     = Signal()
+        self.dbg_gpr_ack_i     = Signal()
+        self.dbg_gpr_addr_o    = Signal(7) #  includes fast SPRs, others?
+        self.dbg_gpr_data_i    = Signal(64)
+
+        # Core logging data
+        self.log_data_i        = Signal(256)
+        self.log_read_addr_i   = Signal(32)
+        self.log_read_data_o   = Signal(64)
+        self.log_write_addr_o  = Signal(32)
+
+        # Misc
+        self.terminated_o  = Signal()
+
+    def elaborate(self, platform):
+
+        m = Module()
+        comb, sync = m.d.comb, m.d.sync
+
+        # DMI needs fixing... make a one clock pulse
+        dmi_req_i_1 = Signal()
+
+        # Some internal wires
+        stat_reg = Signal(64)
+
+        # Some internal latches
+        stopping     = Signal()
+        do_step      = Signal()
+        do_reset     = Signal()
+        do_icreset   = Signal()
+        terminated   = Signal()
+        do_gspr_rd   = Signal()
+        gspr_index   = Signal.like(self.dbg_gpr_addr_o)
+
+        log_dmi_addr = Signal(32)
+        log_dmi_data = Signal(64)
+        do_dmi_log_rd = Signal()
+        dmi_read_log_data = Signal()
+        dmi_read_log_data_1 = Signal()
+
+        LOG_INDEX_BITS = log2_int(self.LOG_LENGTH)
+
+        # Single cycle register accesses on DMI except for GSPR data
+        comb += self.dmi.ack_o.eq(Mux(self.dmi.addr_i == DBGCore.GSPR_DATA,
+                                      self.dbg_gpr_ack_i, self.dmi.req_i))
+        comb += self.dbg_gpr_req_o.eq(Mux(self.dmi.addr_i == DBGCore.GSPR_DATA,
+                                      self.dmi.req_i, 0))
+
+        # Status register read composition (DBUG_CORE_STAT_xxx)
+        comb += stat_reg.eq(Cat(stopping,            # bit 0
+                                self.core_stopped_i, # bit 1
+                                terminated))         # bit 2
+
+        # DMI read data mux
+        with m.Switch(self.dmi.addr_i):
+            with m.Case( DBGCore.STAT):
+                comb += self.dmi.dout.eq(stat_reg)
+            with m.Case( DBGCore.NIA):
+                comb += self.dmi.dout.eq(self.nia)
+            with m.Case( DBGCore.MSR):
+                comb += self.dmi.dout.eq(self.msr)
+            with m.Case( DBGCore.GSPR_DATA):
+                comb += self.dmi.dout.eq(self.dbg_gpr_data_i)
+            with m.Case( DBGCore.LOG_ADDR):
+                comb += self.dmi.dout.eq(Cat(log_dmi_addr,
+                                             self.log_write_addr_o))
+            with m.Case( DBGCore.LOG_DATA):
+                comb += self.dmi.dout.eq(log_dmi_data)
+
+        # DMI writes
+        # Reset the 1-cycle "do" signals
+        sync += do_step.eq(0)
+        sync += do_reset.eq(0)
+        sync += do_icreset.eq(0)
+        sync += do_dmi_log_rd.eq(0)
+
+        # Edge detect on dmi_req_i for 1-shot pulses
+        sync += dmi_req_i_1.eq(self.dmi.req_i)
+        with m.If(self.dmi.req_i & ~dmi_req_i_1):
+            with m.If(self.dmi.we_i):
+                #sync += Display("DMI write to " & to_hstring(dmi_addr))
+
+                # Control register actions
+
+                # Core control
+                with m.If(self.dmi.addr_i == DBGCore.CTRL):
+                    with m.If(self.dmi.din[DBGCtrl.RESET]):
+                        sync += do_reset.eq(1)
+                        sync += terminated.eq(0)
+                    with m.If(self.dmi.din[DBGCtrl.STOP]):
+                        sync += stopping.eq(1)
+                    with m.If(self.dmi.din[DBGCtrl.STEP]):
+                        sync += do_step.eq(1)
+                        sync += terminated.eq(0)
+                    with m.If(self.dmi.din[DBGCtrl.ICRESET]):
+                        sync += do_icreset.eq(1)
+                    with m.If(self.dmi.din[DBGCtrl.START]):
+                        sync += stopping.eq(0)
+                        sync += terminated.eq(0)
+
+                # GSPR address
+                with m.Elif(self.dmi.addr_i == DBGCore.GSPR_INDEX):
+                    sync += gspr_index.eq(self.dmi.din)
+
+                # Log address
+                with m.Elif(self.dmi.addr_i == DBGCore.LOG_ADDR):
+                    sync += log_dmi_addr.eq(self.dmi.din)
+                    sync += do_dmi_log_rd.eq(1)
+            with m.Else():
+                # sync += Display("DMI read from " & to_string(dmi_addr))
+                pass
+
+        with m.Elif(dmi_read_log_data_1 & ~dmi_read_log_data):
+            # Increment log_dmi_addr after end of read from DBGCore.LOG_DATA
+            lds = log_dmi_addr[:LOG_INDEX_BITS+2]
+            sync += lds.eq(lds + 1)
+            sync += do_dmi_log_rd.eq(1)
+
+        sync += dmi_read_log_data_1.eq(dmi_read_log_data)
+        sync += dmi_read_log_data.eq(self.dmi.req_i &
+                                     (self.dmi.addr_i == DBGCore.LOG_DATA))
+
+        # Set core stop on terminate. We'll be stopping some time *after*
+        # the offending instruction, at least until we can do back flushes
+        # that preserve NIA which we can't just yet.
+        with m.If(self.terminate_i):
+            sync += stopping.eq(1)
+            sync += terminated.eq(1)
+
+        comb += self.dbg_gpr_addr_o.eq(gspr_index)
+
+        # Core control signals generated by the debug module
+        comb += self.core_stop_o.eq(stopping & ~do_step)
+        comb += self.core_rst_o.eq(do_reset)
+        comb += self.icache_rst_o.eq(do_icreset)
+        comb += self.terminated_o.eq(terminated)
+
+        # Logging RAM (none)
+
+        if self.LOG_LENGTH == 0:
+            self.log_read_data_o.eq(0)
+            self.log_write_addr_o.eq(0x00000001)
+
+        return m
+
+        # TODO: debug logging
+        """
+        maybe_log: with m.If(LOG_LENGTH > 0 generate
+            subtype log_ptr_t is unsigned(LOG_INDEX_BITS - 1 downto 0)
+            type log_array_t is array(0 to LOG_LENGTH - 1) of std_ulogic_vector(255 downto 0)
+            signal log_array    : log_array_t
+            signal log_rd_ptr   : log_ptr_t
+            signal log_wr_ptr   : log_ptr_t
+            signal log_toggle   = Signal()
+            signal log_wr_enable = Signal()
+            signal log_rd_ptr_latched : log_ptr_t
+            signal log_rd       = Signal()_vector(255 downto 0)
+            signal log_dmi_reading = Signal()
+            signal log_dmi_read_done = Signal()
+
+            function select_dword(data = Signal()_vector(255 downto 0)
+                                  addr = Signal()_vector(31 downto 0)) return std_ulogic_vector is
+                variable firstbit : integer
+            begin
+                firstbit := to_integer(unsigned(addr(1 downto 0))) * 64
+                return data(firstbit + 63 downto firstbit)
+            end
+
+            attribute ram_style : string
+            attribute ram_style of log_array : signal is "block"
+            attribute ram_decomp : string
+            attribute ram_decomp of log_array : signal is "power"
+
+        begin
+            # Use MSB of read addresses to stop the logging
+            log_wr_enable.eq(not (self.log_read_addr(31) or log_dmi_addr(31))
+
+            log_ram: process(clk)
+            begin
+                with m.If(rising_edge(clk)):
+                    with m.If(log_wr_enable = '1'):
+                        log_array(to_integer(log_wr_ptr)).eq(self.log_data
+                    end if
+                    log_rd.eq(log_array(to_integer(log_rd_ptr_latched))
+                end if
+            end process
+
+
+            log_buffer: process(clk)
+                variable b : integer
+                variable data = Signal()_vector(255 downto 0)
+            begin
+                with m.If(rising_edge(clk)):
+                    with m.If(rst = '1'):
+                        log_wr_ptr.eq((others => '0')
+                        log_toggle.eq('0'
+                    with m.Elif(log_wr_enable = '1'):
+                        with m.If(log_wr_ptr = to_unsigned(LOG_LENGTH - 1, LOG_INDEX_BITS)):
+                            log_toggle.eq(not log_toggle
+                        end if
+                        log_wr_ptr.eq(log_wr_ptr + 1
+                    end if
+                    with m.If(do_dmi_log_rd = '1'):
+                        log_rd_ptr_latched.eq(unsigned(log_dmi_addr(LOG_INDEX_BITS + 1 downto 2))
+                    else
+                        log_rd_ptr_latched.eq(unsigned(self.log_read_addr(LOG_INDEX_BITS + 1 downto 2))
+                    end if
+                    with m.If(log_dmi_read_done = '1'):
+                        log_dmi_data.eq(select_dword(log_rd, log_dmi_addr)
+                    else
+                        self.log_read_data.eq(select_dword(log_rd, self.log_read_addr)
+                    end if
+                    log_dmi_read_done.eq(log_dmi_reading
+                    log_dmi_reading.eq(do_dmi_log_rd
+                end if
+            end process
+            self.log_write_addr(LOG_INDEX_BITS - 1 downto 0).eq(std_ulogic_vector(log_wr_ptr)
+            self.log_write_addr(LOG_INDEX_BITS).eq('1'
+            self.log_write_addr(31 downto LOG_INDEX_BITS + 1).eq((others => '0')
+        end generate
+
+        """
+
+    def __iter__(self):
+        yield from self.dmi
+        yield self.core_stop_o
+        yield self.core_rst_o
+        yield self.icache_rst_o
+        yield self.terminate_i
+        yield self.core_stopped_i
+        yield self.nia
+        yield self.msr
+        yield self.dbg_gpr_req_o
+        yield self.dbg_gpr_ack_i
+        yield self.dbg_gpr_addr_o
+        yield self.dbg_gpr_data_i
+        yield self.log_data_i
+        yield self.log_read_addr_i
+        yield self.log_read_data_o
+        yield self.log_write_addr_o
+        yield self.terminated_o
+
+    def ports(self):
+        return list(self)
+
+
+def test_debug():
+
+    dut = CoreDebug()
+    vl = rtlil.convert(dut, ports=dut.ports())
+    with open("test_core_debug.il", "w") as f:
+        f.write(vl)
+
+if __name__ == '__main__':
+    test_debug()
+