From c704b9f1e47b9b720a7384e9fb47e30f3d325c7f Mon Sep 17 00:00:00 2001 From: Ryan <> Date: Sun, 28 Sep 2025 20:27:49 -0500 Subject: [PATCH] Add JTAG USERn registers and test to utility lib --- libraries/utility/jtag_reg.vhd | 81 +++++ libraries/utility/tests/nexys2_test_jtag.vhd | 176 ++++++++++ libraries/utility/tests/program.sh | 4 + projects/nexys2_host_controller/host/build.sh | 2 + projects/nexys2_host_controller/host/jtag.c | 326 ++++++++++++++++++ 5 files changed, 589 insertions(+) create mode 100644 libraries/utility/jtag_reg.vhd create mode 100644 libraries/utility/tests/nexys2_test_jtag.vhd create mode 100755 libraries/utility/tests/program.sh create mode 100644 projects/nexys2_host_controller/host/jtag.c diff --git a/libraries/utility/jtag_reg.vhd b/libraries/utility/jtag_reg.vhd new file mode 100644 index 0000000..69e3da4 --- /dev/null +++ b/libraries/utility/jtag_reg.vhd @@ -0,0 +1,81 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + + +entity jtag_reg is + generic (N: natural := 8); + port ( + -- Device signals + clk_i: in std_logic; + upd_o: out std_logic; + dat_i: in std_logic_vector(N-1 downto 0); + dat_o: out std_logic_vector(N-1 downto 0); + + -- JTAG signals + reset: in std_logic; + capture: in std_logic; + shift: in std_logic; + update: in std_logic; + sel: in std_logic; + drck: in std_logic; + tdi: in std_logic; + tdo: out std_logic + ); +end jtag_reg; + + +architecture behavioral of jtag_reg is + + signal update_cross_reg: std_logic_vector(3 downto 0); + + signal shift_reg: std_logic_vector(N-1 downto 0); + signal outbuf_reg: std_logic_vector(N-1 downto 0); + +begin + + process (reset, capture, shift, update, sel, drck, tdi, shift_reg) + begin + if reset = '1' then + -- Don't need to reset shift_reg, it gets loaded on CAPTURE + null; + elsif sel = '1' then + if rising_edge(drck) and sel = '1' then + if capture = '1' then + shift_reg <= dat_i; + elsif shift = '1' then + shift_reg <= tdi & shift_reg(N-1 downto 1); + end if; + end if; + end if; + end process; + tdo <= shift_reg(0); + + + -- Output register, cross clock domains from DRCK to clk_i + -- This assumes: + -- * JTAG clock is slower than clk_i + -- * shift_reg will be stable while UPDATE is asserted + -- * sel will be stable while UPDATE is asserted + process (clk_i) + begin + if rising_edge(clk_i) then + if reset = '1' then + -- Probably not necessary + update_cross_reg <= (others => '0'); + else + update_cross_reg <= update_cross_reg(2 downto 0) & (update and sel); + + if update_cross_reg(1) = '1' then + outbuf_reg <= shift_reg; + end if; + end if; + end if; + end process; + dat_o <= outbuf_reg; + + -- Extra bits of shift register are for generating single-cycle update + -- signal within clk_i domain + upd_o <= '1' when update_cross_reg(3 downto 2) = "01" else '0'; + +end behavioral; diff --git a/libraries/utility/tests/nexys2_test_jtag.vhd b/libraries/utility/tests/nexys2_test_jtag.vhd new file mode 100644 index 0000000..f4d90c7 --- /dev/null +++ b/libraries/utility/tests/nexys2_test_jtag.vhd @@ -0,0 +1,176 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library unisim; +use unisim.vcomponents.all; + +library work; + +library nexys2_lib; + + +entity nexys2_test_jtag is + port ( + clk_50: in std_logic; + seg: out std_logic_vector(6 downto 0); + dp: out std_logic; + an: out std_logic_vector(3 downto 0); + Led: out std_logic_vector(7 downto 0); + sw: in std_logic_vector(7 downto 0) + ); +end nexys2_test_jtag; + + +architecture behavioral of nexys2_test_jtag is + + signal jtag_capture: std_logic; + signal jtag_drck1: std_logic; + signal jtag_drck2: std_logic; + signal jtag_reset: std_logic; + signal jtag_sel1: std_logic; + signal jtag_sel2: std_logic; + signal jtag_shift: std_logic; + signal jtag_tdi: std_logic; + signal jtag_update: std_logic; + signal jtag_tdo1: std_logic; + signal jtag_tdo2: std_logic; + + signal clk_div_reg: std_logic_vector(15 downto 0); + signal seg_0: std_logic_vector(6 downto 0); + signal seg_1: std_logic_vector(6 downto 0); + signal seg_2: std_logic_vector(6 downto 0); + signal seg_3: std_logic_vector(6 downto 0); + + signal user1: std_logic_vector(3 downto 0); + signal user1_reg: std_logic_vector(3 downto 0); + signal user1_update: std_logic; + signal user1_count_reg: std_logic_vector(7 downto 0); + + signal user2: std_logic_vector(3 downto 0); + signal user2_reg: std_logic_vector(3 downto 0); + signal user2_update: std_logic; + signal user2_count_reg: std_logic_vector(7 downto 0); + +begin + + e_bscan: bscan_spartan3 + port map ( + reset => jtag_reset, + capture => jtag_capture, + shift => jtag_shift, + update => jtag_update, + + tdi => jtag_tdi, + + drck1 => jtag_drck1, + sel1 => jtag_sel1, + tdo1 => jtag_tdo1, + + drck2 => jtag_drck2, + sel2 => jtag_sel2, + tdo2 => jtag_tdo2 + ); + + + e_user1: entity work.jtag_reg + generic map (N => 4) + port map ( + clk_i => clk_50, + upd_o => user1_update, + dat_i => sw(7 downto 4), + dat_o => user1, + + reset => jtag_reset, + capture => jtag_capture, + shift => jtag_shift, + update => jtag_update, + sel => jtag_sel1, + drck => jtag_drck1, + tdi => jtag_tdi, + tdo => jtag_tdo1 + ); + + + e_user2: entity work.jtag_reg + generic map (N => 4) + port map ( + clk_i => clk_50, + upd_o => user2_update, + dat_i => sw(3 downto 0), + dat_o => user2, + + reset => jtag_reset, + capture => jtag_capture, + shift => jtag_shift, + update => jtag_update, + sel => jtag_sel2, + drck => jtag_drck2, + tdi => jtag_tdi, + tdo => jtag_tdo2 + ); + + + process (clk_50) + begin + if rising_edge(clk_50) then + clk_div_reg <= std_logic_vector(unsigned(clk_div_reg) + 1); + + if user1_update = '1' then + user1_reg <= user1; + user1_count_reg <= std_logic_vector(unsigned(user1_count_reg) + 1); + end if; + + if user2_update = '1' then + user2_reg <= user2; + user2_count_reg <= std_logic_vector(unsigned(user2_count_reg) + 1); + end if; + end if; + end process; + Led(7 downto 4) <= user1_reg; + Led(3 downto 0) <= user2_reg; + + + e_seven_seg_hex_0: entity nexys2_lib.seven_seg_hex + port map ( + data_in => user2_count_reg(3 downto 0), + seg_out => seg_0 + ); + + + e_seven_seg_hex_1: entity nexys2_lib.seven_seg_hex + port map ( + data_in => user2_count_reg(7 downto 4), + seg_out => seg_1 + ); + + + e_seven_seg_hex_2: entity nexys2_lib.seven_seg_hex + port map ( + data_in => user1_count_reg(3 downto 0), + seg_out => seg_2 + ); + + + e_seven_seg_hex_3: entity nexys2_lib.seven_seg_hex + port map ( + data_in => user1_count_reg(7 downto 4), + seg_out => seg_3 + ); + + + e_seven_seg_mux: entity nexys2_lib.seven_seg_mux + port map ( + clk_in => clk_div_reg(15), + clk_en => '1', + seg_0_in => seg_0, + seg_1_in => seg_1, + seg_2_in => seg_2, + seg_3_in => seg_3, + dps_in => "1011", + seg_out => seg, + dp_out => dp, + an_out => an + ); + +end behavioral; diff --git a/libraries/utility/tests/program.sh b/libraries/utility/tests/program.sh new file mode 100755 index 0000000..d19c11c --- /dev/null +++ b/libraries/utility/tests/program.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +djtgcfg -d Nexys2 -i 0 -f nexys2_test_jtag.bit prog + diff --git a/projects/nexys2_host_controller/host/build.sh b/projects/nexys2_host_controller/host/build.sh index dcc8e1e..a7e16c6 100755 --- a/projects/nexys2_host_controller/host/build.sh +++ b/projects/nexys2_host_controller/host/build.sh @@ -9,3 +9,5 @@ gcc -o epp_read epp_read.c ec.c $CFLAGS -I $INC -L $LIBDIR -ldepp -ldmgr gcc -o epp_write epp_write.c ec.c $CFLAGS -I $INC -L $LIBDIR -ldepp -ldmgr gcc -o digdude digdude.c fileformat.c memctrl.c ec.c $CFLAGS -I $INC -L $LIBDIR -ldepp -ldmgr +gcc -o jtag jtag.c ec.c $CFLAGS -I $INC -L $LIBDIR -ldmgr -ldjtg + diff --git a/projects/nexys2_host_controller/host/jtag.c b/projects/nexys2_host_controller/host/jtag.c new file mode 100644 index 0000000..c42f544 --- /dev/null +++ b/projects/nexys2_host_controller/host/jtag.c @@ -0,0 +1,326 @@ +#include +#include +#include +#include + +// Digilent SDK +#include +#include +#include + +#include "ec.h" + +#define MAX_ID_CODES 16 +#define MAX_REG_WIDTH 0x1000 +#define SENTINEL 0xdeadbeefcafefeed + +// Debug +#include + + +// ----------------------------------------------------------------------------- + + +uint8_t tms_shift_dr[] = {0x01}; +const uint32_t tms_shift_dr_bits = 3; + +uint8_t tms_shift_ir[] = {0x03}; +const uint32_t tms_shift_ir_bits = 4; + +uint8_t tms_shift_to_idle[] = {0x03}; +const uint32_t tms_shift_to_idle_bits = 3; + +uint64_t window = 0; + + +uint64_t field_read(size_t num_bits) +{ + assert(num_bits <= 64); + uint64_t mask = num_bits == 64 ? ~0ull : (1ull << num_bits) - 1; + return window & mask; +} + + +void field_write(size_t num_bits, uint64_t value) +{ + assert(num_bits <= 64); + uint64_t mask = num_bits == 64 ? ~0ull : (1ull << num_bits) - 1; + window = (window & ~mask) | (value & mask); +} + + +bool tap_reset(HIF hif) +{ + return DjtgClockTck(hif, true, false, 5, false); +} + + +bool tap_reset_to_idle(HIF hif) +{ + return DjtgClockTck(hif, false, false, 1, false); +} + + +bool tap_idle_to_dr(HIF hif) +{ + //return DjtgPutTmsBits(hif, false, tms_shift_dr, NULL, tms_shift_dr_bits, false); + EC_FALSE(DjtgClockTck(hif, true, false, 1, false)); + EC_FALSE(DjtgClockTck(hif, false, false, 1, false)); + EC_FALSE(DjtgClockTck(hif, false, false, 1, false)); + + return true; +EC_CLEANUP_BEGIN + return false; +EC_CLEANUP_END +} + + +bool tap_idle_to_ir(HIF hif) +{ + //return DjtgPutTmsBits(hif, false, tms_shift_ir, NULL, tms_shift_ir_bits, false); + EC_FALSE(DjtgClockTck(hif, true, false, 2, false)); + EC_FALSE(DjtgClockTck(hif, false, false, 1, false)); + EC_FALSE(DjtgClockTck(hif, false, false, 1, false)); + + return true; +EC_CLEANUP_BEGIN + return false; +EC_CLEANUP_END +} + + +bool tap_reg_to_idle(HIF hif) +{ + //return DjtgPutTmsBits(hif, false, tms_shift_to_idle, NULL, tms_shift_to_idle_bits, false); + EC_FALSE(DjtgClockTck(hif, true, false, 2, false)); + EC_FALSE(DjtgClockTck(hif, false, false, 1, false)); + + return true; +EC_CLEANUP_BEGIN + return false; +EC_CLEANUP_END +} + + +bool tap_reg_shift(HIF hif, size_t num_bits, bool final) +{ + assert(hif != hifInvalid); + int tms, tdi, tdo, tck; + + for (size_t i = 0; i < num_bits; i++) + { + EC_FALSE(DjtgGetTmsTdiTdoTck(hif, &tms, &tdi, &tdo, &tck)); + tdi = window & 1; + if (final) + { + EC_FALSE(DjtgClockTck(hif, i == num_bits-1, tdi, 1, false)); + } + else + { + EC_FALSE(DjtgClockTck(hif, false, tdi, 1, false)); + } + window >>= 1; + if (tdo) window |= 0x8000000000000000; + } + if (final) + { + EC_FALSE(DjtgClockTck(hif, true, false, 1, false)); + EC_FALSE(DjtgClockTck(hif, false, false, 1, false)); + } + + return true; +EC_CLEANUP_BEGIN + return false; +EC_CLEANUP_END +} + + +bool tap_reg_get_len(HIF hif, uint32_t *len_out) +{ + assert(hif != hifInvalid); + assert(len_out != NULL); + + uint32_t len = 0; + + field_write(64, SENTINEL); + EC_FALSE(tap_reg_shift(hif, 64, false)); + + for (len = 0; len < MAX_REG_WIDTH; len++) + { + if (field_read(64) == SENTINEL) break; + EC_FALSE(tap_reg_shift(hif, 1, false)); + } + EC_NE(field_read(64), SENTINEL); + + *len_out = len; + + return true; +EC_CLEANUP_BEGIN + return false; +EC_CLEANUP_END +} + + +bool scan(HIF hif) +{ + assert(hif != hifInvalid); + + uint32_t ir_width = 0; + uint32_t bypass_width = 0; + + // Assume IDCODE is selected on reset and scan + printf("IDCODEs:\n"); + EC_FALSE(tap_reset(hif)); + EC_FALSE(tap_reset_to_idle(hif)); + EC_FALSE(tap_idle_to_dr(hif)); + field_write(64, SENTINEL); + EC_FALSE(tap_reg_shift(hif, 64, false)); + for (size_t i = 0; i < MAX_ID_CODES; i++) + { + uint32_t idcode = field_read(32); + printf(" 0x%08x\n", idcode); + EC_FALSE(tap_reg_shift(hif, 32, false)); + if (field_read(64) == SENTINEL) break; + } + EC_NE(field_read(64), SENTINEL); + + // Select IR and find length + EC_FALSE(tap_reset(hif)); + EC_FALSE(tap_reset_to_idle(hif)); + EC_FALSE(tap_idle_to_ir(hif)); + EC_FALSE(tap_reg_get_len(hif, &ir_width)); + printf("JTAG chain total IR width: %u\n", ir_width); + + // Select BYPASS and find length + // TMS TDI + EC_FALSE(DjtgClockTck(hif, false, true, ir_width, false)); + EC_FALSE(tap_reg_to_idle(hif)); + EC_FALSE(tap_idle_to_dr(hif)); + EC_FALSE(tap_reg_get_len(hif, &bypass_width)); + printf("JTAG chain total BYPASS width: %u\n", bypass_width); + + // Reset + EC_FALSE(tap_reset(hif)); + + return true; +EC_CLEANUP_BEGIN + return false; +EC_CLEANUP_END +} + + +// ----------------------------------------------------------------------------- + + +int main(int argc, char **argv) +{ + (void)argc; + (void)argv; + + // Requested settings + char *device = "Nexys2"; + int32_t port = 0; + + // Port handle and properties + HIF hif = hifInvalid; + char version[cchVersionMax]; + int32_t port_count; + uint32_t speed; + + // ------------------------------------------------------------- + + EC_FALSE(DjtgGetVersion(version)); + printf("DJTG version: %s\n", version); + + EC_FALSE(DmgrOpen(&hif, device)); + printf("Opened device \"%s\"\n", device); + + EC_FALSE(DjtgGetPortCount(hif, &port_count)); + printf("Port count: %" PRIi32 "\n", port_count); + for (int32_t i = 0; i < port_count; i++) + { + uint32_t port_properties = 0; + EC_FALSE(DjtgGetPortProperties(hif, i, &port_properties)); + printf("Port %" PRIi32 " properties: 0x%08" PRIx32 "\n", i, port_properties); + printf(" %c JtgSetSpeed\n", port_properties & dprpJtgSetSpeed ? 'y' : 'n'); + printf(" %c JtgSetPinState\n", port_properties & dprpJtgSetPinState ? 'y' : 'n'); + printf(" %c JtgWait\n", port_properties & dprpJtgWait ? 'y' : 'n'); + printf(" %c JtgBatch\n", port_properties & dprpJtgBatch ? 'y' : 'n'); + printf(" %c JtgSetAuxReset\n", port_properties & dprpJtgSetAuxReset ? 'y' : 'n'); + printf(" %c JtgSetGetGpio\n", port_properties & dprpJtgSetGetGpio ? 'y' : 'n'); + + if (port_properties & dprpJtgBatch) + { + uint32_t batch_properties = 0; + EC_FALSE(DjtgGetBatchProperties(hif, i, &batch_properties)); + printf(" Batch properties:\n"); + printf(" %c WaitUs\n", batch_properties & djbpWaitUs ? 'y' : 'n'); + printf(" %c SetAuxReset\n", batch_properties & djbpSetAuxReset ? 'y' : 'n'); + printf(" %c SetGetGpio\n", batch_properties & djbpSetGetGpio ? 'y' : 'n'); + } + } + + if (port >= port_count) + { + fprintf(stderr, "Requested port out of range\n"); + EC_FAIL; + } + + EC_FALSE(DjtgEnableEx(hif, port)); + printf("Opened JTAG port %" PRIi32 "\n", port); + + EC_FALSE(DjtgGetSpeed(hif, &speed)); + printf("Port speed: %" PRIi32 " Hz\n", speed); + + // ------------------------------------------------------------- + + // Perform a scan to see what's on the chain + EC_FALSE(scan(hif)); + printf("\n\n"); + + // Select IR + EC_FALSE(tap_reset(hif)); + EC_FALSE(tap_reset_to_idle(hif)); + EC_FALSE(tap_idle_to_ir(hif)); + + // Insert sentinel + field_write(64, SENTINEL); + EC_FALSE(tap_reg_shift(hif, 64, false)); + + // Write first instruction + field_write(8, 0xff); + EC_FALSE(tap_reg_shift(hif, 7, false)); + + // Write second instruction + field_write(6, 0x03); + EC_FALSE(tap_reg_shift(hif, 6, true)); + + // Select DR + EC_FALSE(tap_idle_to_dr(hif)); + field_write(64, SENTINEL); + EC_FALSE(tap_reg_shift(hif, 64, false)); // Shift out sentinel + EC_FALSE(tap_reg_shift(hif, 1, false)); // Shift out ROM BYPASS + printf("USER1: 0x%1" PRIx64 "\n", field_read(4)); // Read USER1 + field_write(4, 0x3); // Write USER1 + EC_FALSE(tap_reg_shift(hif, 4, true)); // Shift out USER1 + EC_NE(field_read(64), SENTINEL); // Verify sentinel + + // ------------------------------------------------------------- + // Close JTAG session + + if (hif != hifInvalid) + { + DjtgDisable(hif); + DmgrClose(hif); + } + return 0; +EC_CLEANUP_BEGIN + if (hif != hifInvalid) + { + DjtgDisable(hif); + DmgrClose(hif); + } + ec_print(); + return 1; +EC_CLEANUP_END +} -- 2.43.0