From 5e43fea6e20a170502b149e8faf234777df10880 Mon Sep 17 00:00:00 2001 From: Luke Kenneth Casson Leighton Date: Mon, 21 Sep 2020 12:22:26 +0100 Subject: [PATCH] arg complete rewrite of JTAG2DMI, based it on staf (chips4makers) WB --- src/soc/debug/dmi.py | 6 +- src/soc/debug/dmi2jtag.py | 388 ++++++++++++-------------------------- 2 files changed, 119 insertions(+), 275 deletions(-) diff --git a/src/soc/debug/dmi.py b/src/soc/debug/dmi.py index 34c45169..5875813c 100644 --- a/src/soc/debug/dmi.py +++ b/src/soc/debug/dmi.py @@ -52,11 +52,11 @@ class DBGStat: class DMIInterface(RecordObject): - def __init__(self, name): + def __init__(self, name=None): 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.din = Signal(64) # DMI data write in (if we=1) + self.dout = Signal(64) # DMI data read 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 diff --git a/src/soc/debug/dmi2jtag.py b/src/soc/debug/dmi2jtag.py index 7459150e..22dd1ebd 100644 --- a/src/soc/debug/dmi2jtag.py +++ b/src/soc/debug/dmi2jtag.py @@ -1,285 +1,129 @@ -"""JTAGToDMI - -based on Anton Blanchard microwatt dmi_dtm_xilinx.vhdl +"""DMI 2 JTAG +based on Staf Verhaegen (Chips4Makers) wishbone TAP """ -from enum import Enum, unique -from nmigen import (Module, Signal, Elaboratable, Cat, Signal) -from nmigen.cli import main +from nmigen import (Module, Signal, Elaboratable, Const) from nmigen.cli import rtlil -from nmutil.iocontrol import RecordObject -from nmutil.byterev import byte_reverse -from nmutil.mask import Mask -from nmigen.util import log2_int - -# -- Xilinx internal JTAG to DMI interface -# -- -# -- DMI bus -# -- -# -- req : ____/------------\_____ -# -- addr: xxxx< >xxxxx -# -- dout: xxxx< >xxxxx -# -- wr : xxxx< >xxxxx -# -- din : xxxxxxxxxxxx< >xxx -# -- ack : ____________/------\___ -# -- -# -- * addr/dout set along with req, can be latched on same cycle by slave -# -- * ack & din remain up until req is dropped by master, the slave must -# -- provide a stable output on din on reads during that time. -# -- * req remains low at until at least one sysclk after ack seen down. -# -- -# -- JTAG (tck) DMI (sys_clk) -# -- -# -- * jtag_req = 1 -# -- (jtag_req_0) * -# -- (jtag_req_1) -> * dmi_req = 1 > -# -- *.../... -# -- * dmi_ack = 1 < -# -- * (dmi_ack_0) -# -- * <- (dmi_ack_1) -# -- * jtag_req = 0 (and latch dmi_din) -# -- (jtag_req_0) * -# -- (jtag_req_1) -> * dmi_req = 0 > -# -- * dmi_ack = 0 < -# -- * (dmi_ack_0) -# -- * <- (dmi_ack_1) -# -- -# -- jtag_req can go back to 1 when jtag_rsp_1 is 0 -# -- -# -- Questions/TODO: -# -- - I use 2 flip fops for sync, is that enough ? -# -- - I treat the jtag_reset as an async reset, is that necessary ? -# -- - Dbl check reset situation since we have two different resets -# -- each only resetting part of the logic... -# -- - Look at optionally removing the synchronizer on the ack path, -# -- assuming JTAG is always slow enough that ack will have been -# -- stable long enough by the time CAPTURE comes in. -# -- - We could avoid the latched request by not shifting while a -# -- request is in progress (and force TDO to 1 to return a busy -# -- status). -# -- -# -- WARNING: This isn't the real DMI JTAG protocol (at least not yet). -# -- a command while busy will be ignored. A response of "11" -# -- means the previous command is still going, try again. -# -- As such We don't implement the DMI "error" status, and -# -- we don't implement DTMCS yet... This may still all change -# -- but for now it's easier that way as the real DMI protocol -# -- requires for a command to work properly that enough TCK -# -- are sent while IDLE and I'm having trouble getting that -# -- working with UrJtag and the Xilinx BSCAN2 for now. - - -# ** Constants ** -DMI_REQ_NOP = Const(0b00, 2) -DMI_REQ_RD = Const(0b01, 2) -DMI_REQ_WR = Const(0b10, 2) -DMI_RSP_OK = Const(0b00, 2) -DMI_RSP_BSY = Const(0b11, 2) - - -class JTAGToDMI(Elaboratable): - def __init__(self, ABITS=8, DBITS=32): - self.ABITS = ABITS - self.DBITS = DBITS - self.sys_clk = Signal() - self.sys_reset = Signal() - self.dmi_addr = Signal(ABITS) - self.dmi_din = Signal(DBITS) - self.dmi_dout = Signal(DBITS) - self.dmi_req = Signal() - self.dmi_wr = Signal() - self.dmi_ack = Signal() - self.dmi_err = Signal() - - # DMI req synchronization - def dmi_req_sync(self, m, jtag_req): - - # Syncrhonizer for jtag_req (jtag clk -> sys clk) - jtag_req_0 = Signal() - jtag_req_1 = Signal() - - sync = m.d.SYS_sync - - with m.If(sys_reset): - sync += jtag_req_0.eq(0) - sync += jtag_req_1.eq(0) - - with m.Else(): - sync += jtag_req_0.eq(jtag_req) - sync += jtag_req_1.eq(jtag_req_0) - comb += self.dmi_req.eq(jtag_req_1) - - # DMI ack synchronization - def dmi_ack_sync(self, m, dmi_ack_1): - comb = m.d.comb - sync = m.d.JTAG_sync - - dmi_ack_0 = Signal() - - # -- jtag_reset is async (see comments) - with m.If(jtag_reset): - comb += dmi_ack_0.eq(0) - comb += dmi_ack_1.eq(0) - - sync += dmi_ack_0.eq(self.dmi_ack) - sync += dmi_ack_1.eq(dmi_ack_0) - - def elaborate(self, platform): - m = Module() - - comb = m.d.comb - sync = m.d.sync +from c4m.nmigen.jtag.tap import TAP +from soc.debug.dmi import DMIInterface - # Signal coming out of the BSCANE2 block - jtag_reset = Signal() - capture = Signal() - update = Signal() - drck = Signal() - jtag_clk = Signal() - sel = Signal() - shift = Signal() - tdi = Signal() - tdo = Signal() - tck = Signal() - # ** JTAG clock domain ** - - # Shift register - shiftr = Signal(self.ABITS + self.DBITS) - - # Latched request - request = Signal(ABITS + DBITS) - - dmi_ack_1 = Signal() - - # A request is present - jtag_req = Signal() - - # ** SYS clock domain - - # combination signals - jtag_bsy = Signal() - op_valid = Signal() - rsp_op = Signal(2) - - # attribute ASYNC_REG : string; - # attribute ASYNC_REG of jtag_req_0: signal is "TRUE"; - # attribute ASYNC_REG of jtag_req_1: signal is "TRUE"; - # attribute ASYNC_REG of dmi_ack_0: signal is "TRUE"; - # attribute ASYNC_REG of dmi_ack_1: signal is "TRUE"; - # TODO nmigen attributes - # attribute ASYNC_REG : string; - # attribute ASYNC_REG of jtag_req_0: signal is "TRUE"; - # attribute ASYNC_REG of jtag_req_1: signal is "TRUE"; - # attribute ASYNC_REG of dmi_ack_0: signal is "TRUE"; - # attribute ASYNC_REG of dmi_ack_1: signal is "TRUE"; - - -# begin +# JTAG to DMI interface # -# -- Implement the Xilinx bscan2 for series 7 devices (TODO: use PoC -# -- to wrap this if compatibility is required with older devices). -# bscan : BSCANE2 -# generic map ( -# JTAG_CHAIN => 2 -# ) -# port map ( -# CAPTURE => capture, -# DRCK => drck, -# RESET => jtag_reset, -# RUNTEST => open, -# SEL => sel, -# SHIFT => shift, -# TCK => tck, -# TDI => tdi, -# TMS => open, -# UPDATE => update, -# TDO => tdo -# ); +# DMI bus # -# -- Some examples out there suggest buffering the clock so it's -# -- treated as a proper clock net. This is probably needed when using -# -- drck (the gated clock) but I'm using the real tck here to avoid -# -- missing the update phase so maybe not... -# -- -# clkbuf : BUFG -# port map ( -# -- I => drck, -# I => tck, -# O => jtag_clk -# ); +# clk : | | | | | | | +# req : ____/------------\_____ +# addr: xxxx< >xxxxx +# dout: xxxx< >xxxxx +# wr : xxxx< >xxxxx +# din : xxxxxxxxxxxx< >xxx +# ack : ____________/------\___ # - # jtag_bsy indicates whether we can start a new request, - # we can when we aren't already processing one (jtag_req) - # and the synchronized ack of the previous one is 0. - comb += jtag_bsy.eq(jtag_req | dmi_ack_1) +# * addr/dout set along with req, can be latched on same cycle by slave +# * ack & din remain up until req is dropped by master, the slave must +# provide a stable output on din on reads during that time. +# * req remains low at until at least one sysclk after ack seen down. - # -- decode request type in shift register - with m.Switch(shitr[:2]): - with m.Case(DMI_REQ_RD): comb += op_valid.eq(1) - with m.Case(DMI_REQ_WR): comb += op_valid.eq(1) - # -- encode response op - with m.If(jtag_bsy): - comb += rsp_op.eq(DMI_RSP_BSY) - with m.Else(): - comb += rsp_op.eq(DMI_RSP_OK) +class DMITAP(TAP): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._dmis = [] - # Some DMI out signals are directly driven from the request register - # dmi_addr <= request(ABITS + DBITS + 1 downto DBITS + 2); - # dmi_dout <= request(DBITS + 1 downto 2); - # dmi_wr <= '1' when request(1 downto 0) = DMI_REQ_WR else '0'; - comb += dmi_addr.eq(request[DBITS + 2:ABITS + DBITS + 2]) - comb += dmi_dout.eq(request[2:DBITS]) - - with m.If(request[:2] == DMI_REQ_WR): - comb += dmi_wr.eq(1) - - # TDO is wired to shift register bit 0 - comb += tdo.eq(shiftr[0]) - - # Main state machine. Handles shift registers, request latch and - # jtag_req latch. Could be split into 3 processes but it's probably - # not worthwhile. - def shifter(self, m, jtag_clk, jtag_reset) - comb = m.d.comb - sync = m.d.JTAG_sync - - with m.If(jtag_reset): - comb += shiftr.eq(0) - comb += jtag_req.eq(0) - - # Handle jtag "commands" when sel is 1 - with m.Elif(sel): - # Shift state, rotate the register - with m.If(shift): - sync += shiftr.eq(Cat(shiftr[1:ABITS + DBITS], tdi)) - - # Update state (trigger) - # Latch the request if we aren't already processing - # one and it has a valid command opcode. - with m.If(update & op_valid): - with m.If(~jtag_bsy): - sync += request.eq(shiftr) - sync += jtag_req.eq(1) - # Set the shift register "op" to "busy". This will prevent - # us from re-starting the command on the next update if - # the command completes before that. - sync += shiftr[:2].eq(DMI_RSP_BSY) - - # Request completion. - # Capture the response data for reads and clear request flag. - # Note: We clear req (and thus dmi_req) here which - # relies on tck ticking and sel set. This means we - # are stuck with dmi_req up if the jtag interface stops. - # Slaves must be resilient to this. - with m.If(jtag_rq & dmi_ack): - sync += jtag_req.eq(0) - with m.If(request[:2] == DMI_REQ_RD): - sync += request[2:DBITS].eq(dmi_din) - - # Capture state, grab latch content with updated status - with m.If(capture): - sync += shiftr.eq(Cat(rsp_op, request[2:ABITS + DBITS])) + def elaborate(self, platform): + m = super().elaborate(platform) + self._elaborate_dmis(m) + return m + + def add_dmi(self, *, ircodes, address_width=8, data_width=64, + domain="sync", name=None): + """Add a DMI interface + + * writing to DMIADDR will automatically trigger a DMI READ. + the DMI address does not alter (so writes can be done at that addr) + * reading from DMIREAD triggers a DMI READ at the current DMI addr + the address is automatically incremented by 1 after. + * writing to DMIWRITE triggers a DMI WRITE at the current DMI addr + the address is automatically incremented by 1 after. + + Parameters: + ----------- + ircodes: sequence of three integer for the JTAG IR codes; + they represent resp. DMIADDR, DMIREAD and DMIWRITE. + First code has a shift register of length 'address_width', + the two other codes share a shift register of length + data_width. + + address_width: width of the address + data_width: width of the data + + Returns: + dmi: soc.debug.dmi.DMIInterface + The DMI interface + """ + if len(ircodes) != 3: + raise ValueError("3 IR Codes have to be provided") + + if name is None: + name = "dmi" + str(len(self._dmis)) + + # add 2 shift registers: one for addr, one for data. + sr_addr = self.add_shiftreg(ircode=ircodes[0], length=address_width, + domain=domain, name=name+"_addrsr") + sr_data = self.add_shiftreg(ircode=ircodes[1:], length=data_width, + domain=domain, name=name+"_datasr") + + dmi = DMIInterface(name=name) + self._dmis.append((sr_addr, sr_data, dmi, domain)) + + return dmi + + def _elaborate_dmis(self, m): + for sr_addr, sr_data, dmi, domain in self._dmis: + cd = m.d[domain] + m.d.comb += sr_addr.i.eq(dmi.addr_i) + + with m.FSM(domain=domain) as fsm: + + # detect mode based on whether jtag addr or data read/written + with m.State("IDLE"): + with m.If(sr_addr.oe): # DMIADDR code + cd += dmi.addr_i.eq(sr_addr.o) + m.next = "READ" + with m.Elif(sr_data.oe[0]): # DMIREAD code + # If data is + cd += dmi.addr_i.eq(dmi.addr_i + 1) + m.next = "READ" + with m.Elif(sr_data.oe[1]): # DMIWRITE code + cd += dmi.dout.eq(sr_data.o) + m.next = "WRITE" + + # req_i raises for 1 clock + with m.State("READ"): + m.next = "READACK" + + # wait for read ack + with m.State("READACK"): + with m.If(dmi.ack): + # Store read data in sr_data.i hold till next read + cd += sr_data.i.eq(dmi.din) + m.next = "IDLE" + + # req_i raises for 1 clock + with m.State("WRITE"): + m.next = "WRITEACK" + + # wait for write ack + with m.State("WRITEACK"): + with m.If(dmi.ack): + cd += dmi.addr_i.eq(dmi.addr_i + 1) + #m.next = "READ" - for readwrite + + # set DMI req and write-enable based on ongoing FSM states + m.d.comb += [ + dmi.req_i.eq(fsm.ongoing("READ") | fsm.ongoing("WRITE")), + dmi.we_i.eq(fsm.ongoing("WRITE")), + ] -- 2.30.2