dmi_dtm_ecp5: Use ECP5 JTAGG for DMI
authorMatt Johnston <matt@codeconstruct.com.au>
Fri, 26 Nov 2021 02:47:07 +0000 (10:47 +0800)
committerMatt Johnston <matt@codeconstruct.com.au>
Fri, 4 Feb 2022 08:09:28 +0000 (16:09 +0800)
This uses the JTAGG primitive which is similar to BSCANE2.
The LUT4 delay approach came from Florian and Greg in
https://github.com/enjoy-digital/litex/pull/1087

Has been tested on an OrangeCrab with 48MHz sysclk
FT232H up to 30MHz (though libusb/urjtag is by far the bottleneck vs
the JTAG clock)

Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
Makefile
dmi_dtm_ecp5.vhdl [new file with mode: 0644]

index cf723e32e066efd901fbaf1f73b7ce434c2c5da6..85a0fee7c3e1d468cf351f7311261c75bd4c7aec 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -192,6 +192,7 @@ ECP_FLASH_OFFSET=0x80000
 toplevel=fpga/top-orangecrab0.2.vhdl
 litedram_target=orangecrab-85-0.2
 soc_extra_v += litesdcard/generated/lattice/litesdcard_core.v
+dmi_dtm=dmi_dtm_ecp5.vhdl
 endif
 
 # ECP5-EVN
diff --git a/dmi_dtm_ecp5.vhdl b/dmi_dtm_ecp5.vhdl
new file mode 100644 (file)
index 0000000..b2d6ad8
--- /dev/null
@@ -0,0 +1,298 @@
+library ieee;
+use ieee.std_logic_1164.all;
+use ieee.math_real.all;
+
+library work;
+use work.wishbone_types.all;
+
+entity dmi_dtm is
+    generic(ABITS : INTEGER:=8;
+            DBITS : INTEGER:=64);
+
+    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 JTAGG block
+    signal jtag_reset_n : std_ulogic;
+    signal tdi        : std_ulogic;
+    signal tdo        : std_ulogic;
+    signal tck        : std_ulogic;
+    signal jce1       : std_ulogic;
+    signal jshift     : std_ulogic;
+    signal update     : std_ulogic;
+
+    -- signals to match dmi_dtb_xilinx
+    signal jtag_reset : std_ulogic;
+    signal capture    : std_ulogic;
+    signal jtag_clk   : std_ulogic;
+    signal sel        : std_ulogic;
+    signal shift      : std_ulogic;
+
+    -- delays
+    signal jce1_d     : std_ulogic;
+    constant TCK_DELAY : INTEGER := 8;
+    signal tck_d : std_ulogic_vector(TCK_DELAY+1 downto 1);
+
+    -- ** 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";
+
+    -- ECP5 JTAGG
+    component JTAGG is
+        generic (
+            ER1 : string := "ENABLED";
+            ER2 : string := "ENABLED"
+        );
+        port(
+            JTDO1 : in std_ulogic;
+            JTDO2 : in std_ulogic;
+            JTDI : out std_ulogic;
+            JTCK : out std_ulogic;
+            JRTI1 : out std_ulogic;
+            JRTI2 : out std_ulogic;
+            JSHIFT : out std_ulogic;
+            JUPDATE : out std_ulogic;
+            JRSTN : out std_ulogic;
+            JCE1 : out std_ulogic;
+            JCE2 : out std_ulogic
+        );
+    end component;
+
+    component LUT4 is
+        generic (
+            INIT : std_logic_vector
+        );
+        port(
+          A : in STD_ULOGIC;
+          B : in STD_ULOGIC;
+          C : in STD_ULOGIC;
+          D : in STD_ULOGIC;
+          Z : out STD_ULOGIC
+        );
+    end component;
+
+begin
+
+    jtag: JTAGG
+        generic map(
+            ER2 => "DISABLED"
+        )
+        port map (
+            JTDO1 => tdo,
+            JTDO2 => '0',
+            JTDI => tdi,
+            JTCK => tck,
+            JRTI1 => open,
+            JRTI2 => open,
+            JSHIFT => jshift,
+            JUPDATE => update,
+            JRSTN => jtag_reset_n,
+            JCE1 => jce1,
+            JCE2 => open
+        );
+
+    -- JRTI1 looks like it could be connected to SEL, but
+    -- in practise JRTI1 is only high briefly, not for the duration
+    -- of the transmission. possibly mw_debug could be modified.
+    -- The ecp5 is probably the only jtag device anyway.
+    sel <= '1';
+
+    -- TDI needs to align with TCK, we use LUT delays here.
+    -- From https://github.com/enjoy-digital/litex/pull/1087
+    tck_d(1) <= tck;
+    del: for i in 1 to TCK_DELAY generate
+        attribute keep : boolean;
+        attribute keep of l: label is true;
+    begin
+        l: LUT4
+            generic map(
+                INIT => b"0000_0000_0000_0010"
+            )
+            port map (
+                A => tck_d(i),
+                B => '0', C => '0', D => '0',
+                Z => tck_d(i+1)
+            );
+    end generate;
+    jtag_clk <= tck_d(TCK_DELAY+1);
+
+    -- capture signal
+    jce1_sync : process(jtag_clk)
+    begin
+        if rising_edge(jtag_clk) then
+            jce1_d <= jce1;
+            capture <= jce1 and not jce1_d;
+        end if;
+    end process;
+
+    -- latch the shift signal, otherwise
+    -- we miss the last shift in
+    -- (maybe because we are delaying tck?)
+    shift_sync : process(jtag_clk)
+    begin
+        if (sys_reset = '1') then
+            shift <= '0';
+        elsif rising_edge(jtag_clk) then
+            shift <= jshift;
+        end if;
+    end process;
+
+    jtag_reset <= not jtag_reset_n;
+
+    -- 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, sys_reset)
+    begin
+        if jtag_reset = '1' or sys_reset = '1' then
+            shiftr <= (others => '0');
+            jtag_req <= '0';
+            request <= (others => '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;
+