From 4722ce3bd23e2f930099c13ee1f02d00d083ec0d Mon Sep 17 00:00:00 2001 From: Cole Poirier Date: Wed, 16 Sep 2020 13:48:08 -0700 Subject: [PATCH] initial commit of JTAGToDMI debug interface translated from microwatt/dmi_dtm_xilinx.vhdl --- src/soc/experiment/dmi_dtm_xilinx.py | 296 +++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 src/soc/experiment/dmi_dtm_xilinx.py diff --git a/src/soc/experiment/dmi_dtm_xilinx.py b/src/soc/experiment/dmi_dtm_xilinx.py new file mode 100644 index 00000000..13ad20ce --- /dev/null +++ b/src/soc/experiment/dmi_dtm_xilinx.py @@ -0,0 +1,296 @@ +"""JTAGToDMI + +based on Anton Blanchard microwatt dmi_dtm_xilinx.vhdl + +""" + +from enum import Enum, unique +from nmigen import (Module, Signal, Elaboratable, Cat, Signal) +from nmigen.cli import main +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. +# +# library ieee; +# use ieee.std_logic_1164.all; +# use ieee.math_real.all; +# +# library work; +# use work.wishbone_types.all; +# +# library unisim; +# use unisim.vcomponents.all; +# +# entity dmi_dtm is +# generic(ABITS : INTEGER:=8; +# DBITS : INTEGER:=32); +# +# port(sys_clk : in std_ulogic; +# sys_reset : in std_ulogic; +# dmi_addr : out std_ulogic_vector(ABITS - 1 downto 0); +# dmi_din : in std_ulogic_vector(DBITS - 1 downto 0); +# dmi_dout : out std_ulogic_vector(DBITS - 1 downto 0); +# dmi_req : out std_ulogic; +# dmi_wr : out std_ulogic; +# dmi_ack : in std_ulogic +# -- dmi_err : in std_ulogic TODO: Add error response +# ); +# end entity dmi_dtm; +# +# architecture behaviour of dmi_dtm is +# +# -- Signals coming out of the BSCANE2 block +# signal jtag_reset : std_ulogic; +# signal capture : std_ulogic; +# signal update : std_ulogic; +# signal drck : std_ulogic; +# signal jtag_clk : std_ulogic; +# signal sel : std_ulogic; +# signal shift : std_ulogic; +# signal tdi : std_ulogic; +# signal tdo : std_ulogic; +# signal tck : std_ulogic; +# +# -- ** JTAG clock domain ** +# +# -- Shift register +# signal shiftr : std_ulogic_vector(ABITS + DBITS + 1 downto 0); +# +# -- Latched request +# signal request : std_ulogic_vector(ABITS + DBITS + 1 downto 0); +# +# -- A request is present +# signal jtag_req : std_ulogic; +# +# -- Synchronizer for jtag_rsp (sys clk -> jtag_clk) +# signal dmi_ack_0 : std_ulogic; +# signal dmi_ack_1 : std_ulogic; +# +# -- ** sys clock domain ** +# +# -- Synchronizer for jtag_req (jtag clk -> sys clk) +# signal jtag_req_0 : std_ulogic; +# signal jtag_req_1 : std_ulogic; +# +# -- ** combination signals +# signal jtag_bsy : std_ulogic; +# signal op_valid : std_ulogic; +# signal rsp_op : std_ulogic_vector(1 downto 0); +# +# -- ** Constants ** +# constant DMI_REQ_NOP : std_ulogic_vector(1 downto 0) := "00"; +# constant DMI_REQ_RD : std_ulogic_vector(1 downto 0) := "01"; +# constant DMI_REQ_WR : std_ulogic_vector(1 downto 0) := "10"; +# constant DMI_RSP_OK : std_ulogic_vector(1 downto 0) := "00"; +# constant DMI_RSP_BSY : std_ulogic_vector(1 downto 0) := "11"; +# +# 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 +# +# -- 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 +# ); +# +# -- 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 +# ); +# +# -- dmi_req synchronization +# dmi_req_sync : process(sys_clk) +# begin +# -- sys_reset is synchronous +# if rising_edge(sys_clk) then +# if (sys_reset = '1') then +# jtag_req_0 <= '0'; +# jtag_req_1 <= '0'; +# else +# jtag_req_0 <= jtag_req; +# jtag_req_1 <= jtag_req_0; +# end if; +# end if; +# end process; +# dmi_req <= jtag_req_1; +# +# -- dmi_ack synchronization +# dmi_ack_sync: process(jtag_clk, jtag_reset) +# begin +# -- jtag_reset is async (see comments) +# if jtag_reset = '1' then +# dmi_ack_0 <= '0'; +# dmi_ack_1 <= '0'; +# elsif rising_edge(jtag_clk) then +# dmi_ack_0 <= dmi_ack; +# dmi_ack_1 <= dmi_ack_0; +# end if; +# end process; +# +# -- 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. +# -- +# jtag_bsy <= jtag_req or dmi_ack_1; +# +# -- decode request type in shift register +# with shiftr(1 downto 0) select op_valid <= +# '1' when DMI_REQ_RD, +# '1' when DMI_REQ_WR, +# '0' when others; +# +# -- encode response op +# rsp_op <= DMI_RSP_BSY when jtag_bsy = '1' else DMI_RSP_OK; +# +# -- 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'; +# +# -- TDO is wired to shift register bit 0 +# tdo <= 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. +# -- +# shifter: process(jtag_clk, jtag_reset) +# begin +# if jtag_reset = '1' then +# shiftr <= (others => '0'); +# jtag_req <= '0'; +# elsif rising_edge(jtag_clk) then +# +# -- Handle jtag "commands" when sel is 1 +# if sel = '1' then +# -- Shift state, rotate the register +# if shift = '1' then +# shiftr <= tdi & shiftr(ABITS + DBITS + 1 downto 1); +# end if; +# +# -- Update state (trigger) +# -- +# -- Latch the request if we aren't already processing +# -- one and it has a valid command opcode. +# -- +# if update = '1' and op_valid = '1' then +# if jtag_bsy = '0' then +# request <= shiftr; +# jtag_req <= '1'; +# end if; +# -- 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. +# shiftr(1 downto 0) <= DMI_RSP_BSY; +# end if; +# +# -- 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. +# -- +# if jtag_req = '1' and dmi_ack_1 = '1' then +# jtag_req <= '0'; +# if request(1 downto 0) = DMI_REQ_RD then +# request(DBITS + 1 downto 2) <= dmi_din; +# end if; +# end if; +# +# -- Capture state, grab latch content with updated status +# if capture = '1' then +# shiftr <= request(ABITS + DBITS + 1 downto 2) & rsp_op; +# end if; +# +# end if; +# end if; +# end process; +# end architecture behaviour; -- 2.30.2