diff --git a/project.cfg b/project.cfg index c0965b0..ec8fc15 100644 --- a/project.cfg +++ b/project.cfg @@ -21,7 +21,7 @@ device = xc6slx9 package = tqg144 speedgrade = -2 toplevel = top_generic -xst_opts = -vlgincdir rtl/util +xst_opts = -vlgincdir rtl/util -keep_hierarchy yes files_verilog = rtl/util/conv.vh rtl/toplevel/top_generic.v rtl/core/nco_q15.v @@ -59,7 +59,8 @@ files_verilog = rtl/util/conv.vh rtl/serv/serving.v rtl/wb/wb_gpio.v rtl/wb/wb_gpio_banks.v - rtl/core/soclet.v + rtl/arch/spartan-6/jtag_if.v + rtl/core/mcu.v files_con = boards/mimas_v1/constraints.ucf files_other = rtl/util/rc_alpha_q15.vh rtl/util/clog2.vh diff --git a/rtl/arch/spartan-6/jtag_if.v b/rtl/arch/spartan-6/jtag_if.v index 53da708..6d84f68 100644 --- a/rtl/arch/spartan-6/jtag_if.v +++ b/rtl/arch/spartan-6/jtag_if.v @@ -29,6 +29,7 @@ module jtag_if #( .SHIFT(o_shift), .TCK(o_tck), .TDI(o_tdi), + .TDO(i_tdo), .UPDATE(o_update) ); endmodule diff --git a/rtl/core/mcu.v b/rtl/core/mcu.v new file mode 100644 index 0000000..55f1872 --- /dev/null +++ b/rtl/core/mcu.v @@ -0,0 +1,414 @@ +`timescale 1ns/1ps +`include "../util/clog2.vh" + +module mcu #( + parameter memfile = "", + parameter memsize = 8192, + parameter sim = 1'b0 +)( + input wire i_clk, + input wire i_rst, + + input wire [31:0] i_GPI_A, + input wire [31:0] i_GPI_B, + input wire [31:0] i_GPI_C, + input wire [31:0] i_GPI_D, + output wire [31:0] o_GPO_A, + output wire [31:0] o_GPO_B, + output wire [31:0] o_GPO_C, + output wire [31:0] o_GPO_D +); + localparam WITH_CSR = 1; + localparam regs = 32+WITH_CSR*4; + localparam rf_width = 8; + + wire rst; + wire rst_mem_reason; + wire timer_irq; + assign rst = i_rst | rst_mem_reason; + assign timer_irq = 1'b0; + + // Busses + // CPU->memory + wire [31:0] wb_mem_adr; + wire [31:0] wb_mem_dat; + wire [3:0] wb_mem_sel; + wire wb_mem_we; + wire wb_mem_stb; + wire [31:0] wb_mem_rdt; + wire wb_mem_ack; + // CPU->peripherals + wire [31:0] wb_ext_adr; + wire [31:0] wb_ext_dat; + wire [3:0] wb_ext_sel; + wire wb_ext_we; + wire wb_ext_stb; + wire [31:0] wb_ext_rdt; + wire wb_ext_ack; + // CPU->RF + wire [6+WITH_CSR:0] rf_waddr; + wire [rf_width-1:0] rf_wdata; + wire rf_wen; + wire [6+WITH_CSR:0] rf_raddr; + wire [rf_width-1:0] rf_rdata; + wire rf_ren; + // combined RF and mem bus to actual RAM + wire [`CLOG2(memsize)-1:0] sram_waddr; + wire [rf_width-1:0] sram_wdata; + wire sram_wen; + wire [`CLOG2(memsize)-1:0] sram_raddr; + wire [rf_width-1:0] sram_rdata; + wire sram_ren; + + // GPIO + wire [4*32-1:0] GPO; + wire [4*32-1:0] GPI; + assign o_GPO_A = GPO[32*1-1:32*0]; + assign o_GPO_B = GPO[32*2-1:32*1]; + assign o_GPO_C = GPO[32*3-1:32*2]; + assign o_GPO_D = GPO[32*4-1:32*3]; + assign GPI[32*1-1:32*0] = i_GPI_A; + assign GPI[32*2-1:32*1] = i_GPI_B; + assign GPI[32*3-1:32*2] = i_GPI_C; + assign GPI[32*4-1:32*3] = i_GPI_D; + + // SERV core with mux splitting dbus into mem and ext and + // arbiter combining mem and ibus + // separate rst line to let other hardware keep core under reset + servile #( + .reset_pc(32'h0000_0000), + .reset_strategy("MINI"), + .rf_width(rf_width), + .sim(sim), + .with_csr(WITH_CSR), + .with_c(0), + .with_mdu(0) + ) servile ( + .i_clk(i_clk), + .i_rst(rst), + .i_timer_irq(timer_irq), + + //Memory interface + .o_wb_mem_adr(wb_mem_adr), + .o_wb_mem_dat(wb_mem_dat), + .o_wb_mem_sel(wb_mem_sel), + .o_wb_mem_we(wb_mem_we), + .o_wb_mem_stb(wb_mem_stb), + .i_wb_mem_rdt(wb_mem_rdt), + .i_wb_mem_ack(wb_mem_ack), + + //Extension interface + .o_wb_ext_adr(wb_ext_adr), + .o_wb_ext_dat(wb_ext_dat), + .o_wb_ext_sel(wb_ext_sel), + .o_wb_ext_we(wb_ext_we), + .o_wb_ext_stb(wb_ext_stb), + .i_wb_ext_rdt(wb_ext_rdt), + .i_wb_ext_ack(wb_ext_ack), + + //RF IF + .o_rf_waddr(rf_waddr), + .o_rf_wdata(rf_wdata), + .o_rf_wen(rf_wen), + .o_rf_raddr(rf_raddr), + .o_rf_ren(rf_ren), + .i_rf_rdata(rf_rdata) + ); + + // WB arbiter combining RF and mem interfaces into 1 + // Last 128 bytes are used for registers + servile_rf_mem_if #( + .depth(memsize), + .rf_regs(regs) + ) rf_mem_if ( + .i_clk (i_clk), + .i_rst (i_rst), + + .i_waddr(rf_waddr), + .i_wdata(rf_wdata), + .i_wen(rf_wen), + .i_raddr(rf_raddr), + .o_rdata(rf_rdata), + .i_ren(rf_ren), + + .o_sram_waddr(sram_waddr), + .o_sram_wdata(sram_wdata), + .o_sram_wen(sram_wen), + .o_sram_raddr(sram_raddr), + .i_sram_rdata(sram_rdata), + // .o_sram_ren(sram_ren), + + .i_wb_adr(wb_mem_adr[`CLOG2(memsize)-1:2]), + .i_wb_stb(wb_mem_stb), + .i_wb_we(wb_mem_we) , + .i_wb_sel(wb_mem_sel), + .i_wb_dat(wb_mem_dat), + .o_wb_rdt(wb_mem_rdt), + .o_wb_ack(wb_mem_ack) + ); + + memory #( + .memfile(memfile), + .depth(memsize), + .sim(sim) + ) mem ( + .i_clk(i_clk), + .i_waddr(sram_waddr), + .i_wdata(sram_wdata), + .i_wen(sram_wen), + .i_raddr(sram_raddr), + .o_rdata(sram_rdata), + .o_core_reset(rst_mem_reason) + ); + + wb_gpio_banks #( + .BASE_ADDR(32'h40000000), + .NUM_BANKS(4) + ) gpio ( + .i_wb_clk(i_clk), + .i_wb_rst(rst), + .i_wb_dat(wb_ext_dat), + .i_wb_adr(wb_ext_adr), + .i_wb_we(wb_ext_we), + .i_wb_stb(wb_ext_stb), + .i_wb_sel(wb_ext_sel), + .o_wb_rdt(wb_ext_rdt), + .o_wb_ack(wb_ext_ack), + .i_gpio(GPI), + .o_gpio(GPO) + ); + +endmodule + +module jtag_mem_protocol #( + parameter aw = 8, + parameter chain = 1 +)( + input wire i_clk, + input wire i_rst, + input wire [7:0] i_mem_rdata, + output reg o_mem_wen, + output reg [7:0] o_mem_wdata, + output reg [aw-1:0] o_mem_waddr, + output reg [aw-1:0] o_mem_raddr, + output reg o_core_reset +); + localparam FRAME_BITS = 42; + + // Frame format (LSB-first on JTAG shift stream): + // [0] : core reset + // [1] : write (1=write, 0=read) + // [33:2] : address + // [41:34] : data + wire [31:0] cmd_addr_full; + wire cmd_reset; + wire cmd_write; + wire [7:0] cmd_data; + + // JTAG interface wires + wire jtag_tck; + wire jtag_tdi; + wire jtag_drck; + wire jtag_capture; + wire jtag_shift; + wire jtag_update; + wire jtag_runtest; + wire jtag_reset; + wire jtag_sel; + + // JTAG clock-domain regs + reg [FRAME_BITS-1:0] shift_q; + reg [FRAME_BITS-1:0] cmd_jtag_q; + reg update_toggle_jtag_q; + reg [FRAME_BITS-1:0] resp_meta_q; + reg [FRAME_BITS-1:0] resp_sync_q; + + // Core clock-domain CDC regs + reg update_toggle_meta_q; + reg update_toggle_sync_q; + reg update_toggle_sync_d_q; + reg [FRAME_BITS-1:0] cmd_meta_q; + reg [FRAME_BITS-1:0] cmd_sync_q; + + // Core clock-domain protocol state + reg [FRAME_BITS-1:0] resp_core_q; + reg read_pending_q; + reg [31:0] read_addr_full_q; + + assign cmd_reset = cmd_sync_q[0]; + assign cmd_write = cmd_sync_q[1]; + assign cmd_addr_full = cmd_sync_q[33:2]; + assign cmd_data = cmd_sync_q[41:34]; + + jtag_if #( + .chain(chain) + ) jtag ( + .i_tdo(shift_q[0]), + .o_tck(jtag_tck), + .o_tdi(jtag_tdi), + .o_drck(jtag_drck), + .o_capture(jtag_capture), + .o_shift(jtag_shift), + .o_update(jtag_update), + .o_runtest(jtag_runtest), + .o_reset(jtag_reset), + .o_sel(jtag_sel) + ); + + // JTAG domain: shift register and update event capture. + always @(posedge jtag_drck or posedge jtag_reset or posedge i_rst) begin + if (jtag_reset || i_rst) begin + shift_q <= {FRAME_BITS{1'b0}}; + resp_meta_q <= {FRAME_BITS{1'b0}}; + resp_sync_q <= {FRAME_BITS{1'b0}}; + end else begin + resp_meta_q <= resp_core_q; + resp_sync_q <= resp_meta_q; + + if (jtag_sel && jtag_capture) begin + shift_q <= resp_sync_q; + end else if (jtag_sel && jtag_shift) begin + shift_q <= {jtag_tdi, shift_q[FRAME_BITS-1:1]}; + end + end + end + + always @(posedge jtag_update or posedge jtag_reset or posedge i_rst) begin + if (jtag_reset || i_rst) begin + cmd_jtag_q <= {FRAME_BITS{1'b0}}; + update_toggle_jtag_q <= 1'b0; + end else if (jtag_sel) begin + cmd_jtag_q <= shift_q; + update_toggle_jtag_q <= ~update_toggle_jtag_q; + end + end + + // Core domain: receive command and execute against RAM port-B. + always @(posedge i_clk or posedge i_rst) begin + if (i_rst) begin + update_toggle_meta_q <= 1'b0; + update_toggle_sync_q <= 1'b0; + update_toggle_sync_d_q <= 1'b0; + cmd_meta_q <= {FRAME_BITS{1'b0}}; + cmd_sync_q <= {FRAME_BITS{1'b0}}; + o_mem_wen <= 1'b0; + o_mem_wdata <= 8'h00; + o_mem_waddr <= {aw{1'b0}}; + o_mem_raddr <= {aw{1'b0}}; + o_core_reset <= 1'b0; + resp_core_q <= {FRAME_BITS{1'b0}}; + read_pending_q <= 1'b0; + read_addr_full_q <= 32'h0000_0000; + end else begin + // Defaults are one-cycle strobes for write/reset. + o_mem_wen <= 1'b0; + o_core_reset <= 1'b0; + + update_toggle_meta_q <= update_toggle_jtag_q; + update_toggle_sync_q <= update_toggle_meta_q; + update_toggle_sync_d_q <= update_toggle_sync_q; + cmd_meta_q <= cmd_jtag_q; + cmd_sync_q <= cmd_meta_q; + + // Complete pending read (port-B read is synchronous). + if (read_pending_q) begin + read_pending_q <= 1'b0; + resp_core_q[0] <= 1'b0; + resp_core_q[1] <= 1'b0; + resp_core_q[33:2] <= read_addr_full_q; + resp_core_q[41:34] <= i_mem_rdata; + end + + // New JTAG command arrived. + if (update_toggle_sync_q ^ update_toggle_sync_d_q) begin + if (cmd_reset) begin + o_core_reset <= 1'b1; + end + + if (cmd_write) begin + o_mem_waddr <= cmd_addr_full[aw-1:0]; + o_mem_wdata <= cmd_data; + o_mem_wen <= 1'b1; + resp_core_q[0] <= cmd_reset; + resp_core_q[1] <= 1'b1; + resp_core_q[33:2] <= cmd_addr_full; + resp_core_q[41:34] <= cmd_data; + end else begin + o_mem_raddr <= cmd_addr_full[aw-1:0]; + read_pending_q <= 1'b1; + read_addr_full_q <= cmd_addr_full; + end + end + end + end + + // Keep lint quiet for currently-unused JTAG outputs. + wire _unused_tck; + wire _unused_runtest; + assign _unused_tck = jtag_tck; + assign _unused_runtest = jtag_runtest; +endmodule + +module memory #( + parameter memfile = "", + parameter depth = 256, + parameter sim = 1'b0, + localparam aw = `CLOG2(depth) +)( + input wire i_clk, + input wire [aw-1:0] i_waddr, + input wire [7:0] i_wdata, + input wire i_wen, + input wire [aw-1:0] i_raddr, + output reg [7:0] o_rdata, + output wire o_core_reset +); + // The actual memory + reg [7:0]mem [0:depth-1]; + + // Second interface + wire wen_b; + wire [7:0] wdata_b; + wire [aw-1:0] waddr_b; + reg [7:0] rdata_b; + wire [aw-1:0] raddr_b; + jtag_mem_protocol #( + .aw(aw), + .chain(1) + ) jtag_mem ( + .i_clk(i_clk), + .i_rst(1'b0), + .i_mem_rdata(rdata_b), + .o_mem_wen(wen_b), + .o_mem_wdata(wdata_b), + .o_mem_waddr(waddr_b), + .o_mem_raddr(raddr_b), + .o_core_reset(o_core_reset) + ); + + // Read/Write + always @(posedge i_clk) begin + // main interface + if (i_wen) + mem[i_waddr] <= i_wdata; + o_rdata <= mem[i_raddr]; + // second interface + if (wen_b) + mem[waddr_b] <= wdata_b; + rdata_b <= mem[raddr_b]; + end + + // Preload memory + integer i; + initial begin + if(sim==1'b1) begin + for (i = 0; i < depth; i = i + 1) + mem[i] = 8'h00; + end + if(|memfile) begin + $display("Preloading %m from %s", memfile); + $readmemh(memfile, mem); + end + end + +endmodule diff --git a/rtl/toplevel/top_generic.v b/rtl/toplevel/top_generic.v index a3eeb3a..a3adde1 100644 --- a/rtl/toplevel/top_generic.v +++ b/rtl/toplevel/top_generic.v @@ -7,11 +7,13 @@ module top_generic( output wire led_green, output wire led_red, - output wire[5:0] r2r + output wire[5:0] r2r, + output wire[7:0] LED ); `include "conv.vh" assign led_green = 1'b0; assign led_red = 1'b0; + assign LED = 8'h00; // Clocking wire clk_100; @@ -27,7 +29,7 @@ module top_generic( wire [31:0] GPIO_C; wire [31:0] GPIO_D; - soclet #( + mcu #( .memfile("../sw/sweep/sweep.hex") ) mcu ( .i_clk(clk_15), diff --git a/scripts/jtag_write_user_impact.py b/scripts/jtag_write_user_impact.py deleted file mode 100755 index b206e4b..0000000 --- a/scripts/jtag_write_user_impact.py +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env python3 -"""Write USER JTAG data via iMPACT using SVF + play flow.""" - -from __future__ import annotations - -import argparse -import os -import shutil -import subprocess -import sys -import tempfile - - -DEFAULT_IMPACT = "/opt/Xilinx/14.7/ISE_DS/ISE/bin/lin64/impact" -DEFAULT_SETTINGS = "/opt/Xilinx/14.7/ISE_DS/settings64.sh" - - -def _parse_int(value: str) -> int: - return int(value, 0) - - -def _resolve_impact(binary: str | None) -> str | None: - if binary: - return binary if os.path.isfile(binary) else shutil.which(binary) - if os.path.isfile(DEFAULT_IMPACT): - return DEFAULT_IMPACT - return shutil.which("impact") - - -def _fmt_hex_for_bits(value: int, bits: int) -> str: - masked = value & ((1 << bits) - 1) - nibbles = (bits + 3) // 4 - return f"{masked:0{nibbles}X}" - - -def main() -> int: - parser = argparse.ArgumentParser( - description="Write USER JTAG data to Spartan-6 via iMPACT play (SVF)." - ) - parser.add_argument( - "--input", - required=True, - help="Input file to stream over USER JTAG (written byte-by-byte).", - ) - parser.add_argument("--output", type=str, default=None) - parser.add_argument("--dr-bits", type=int, default=8, help="DR width in bits (default: 8).") - parser.add_argument("--ir-value", type=_parse_int, default=0x02, help="IR opcode (default USER1: 0x02).") - parser.add_argument("--ir-bits", type=int, default=6, help="IR width in bits (default: 6).") - parser.add_argument("--serial", default=None, help="Cable ESN/serial (example: D306180FABCD).") - parser.add_argument("--impact", default=None, help=f"Path/name of impact binary (default: {DEFAULT_IMPACT}).") - parser.add_argument( - "--settings", - default=DEFAULT_SETTINGS, - help=f"Xilinx settings script sourced for this run only (default: {DEFAULT_SETTINGS}).", - ) - parser.add_argument("--dry-run", action="store_true", help="Print generated SVF/CMD and exit.") - args = parser.parse_args() - - if args.dr_bits <= 0 or args.ir_bits <= 0: - print("error: --dr-bits and --ir-bits must be > 0", file=sys.stderr) - return 2 - if args.dr_bits != 8: - print("error: this file-stream mode currently supports only --dr-bits 8", file=sys.stderr) - return 2 - - impact = _resolve_impact(args.impact) - if impact is None: - print("error: impact binary not found", file=sys.stderr) - return 127 - if not os.path.isfile(args.input): - print(f'error: input file not found: "{args.input}"', file=sys.stderr) - return 2 - - ir_hex = _fmt_hex_for_bits(args.ir_value, args.ir_bits) - with open(args.input, "rb") as f: - payload = f.read() - if len(payload) == 0: - print("error: input file is empty", file=sys.stderr) - return 2 - - svf_lines = [ - "! Auto-generated by jtag_write_user_impact.py", - f"! Source file: {args.input} ({len(payload)} bytes)", - "TRST ABSENT;", - "ENDIR IDLE;", - "ENDDR IDLE;", - "STATE RESET;", - "STATE IDLE;", - f"SIR {args.ir_bits} TDI ({ir_hex});", - ] - for byte in payload: - svf_lines.append(f"SDR 8 TDI ({byte:02X});") - svf_lines += [ - "RUNTEST 16 TCK;", - "STATE IDLE;", - "", - ] - svf_text = "\n".join(svf_lines) - - cable_cmd = "setCable -port auto" - if args.serial: - cable_cmd += f" -esn {args.serial}" - - if args.dry_run: - print("### SVF ###") - preview_limit = 64 - if len(payload) <= preview_limit: - print(svf_text, end="") - else: - preview = "\n".join(svf_lines[:8 + preview_limit]) - print(preview) - print(f"... ({len(payload) - preview_limit} more SDR lines omitted)") - print("### iMPACT CMD ###") - print("setMode -bs") - print(cable_cmd) - print("addDevice -p 1 -file ") - print("play") - print("quit") - return 0 - - svf_path = None - cmd_path = None - try: - with tempfile.NamedTemporaryFile("w", suffix=".svf", delete=False) as sf: - sf.write(svf_text) - svf_path = sf.name - if args.output is not None: - with open(args.output, "w") as sf: - sf.write(svf_text) - - cmd_text = "\n".join( - [ - "setMode -bs", - cable_cmd, - f'addDevice -p 1 -file "{svf_path}"', - "play", - "quit", - "", - ] - ) - with tempfile.NamedTemporaryFile("w", suffix=".cmd", delete=False) as cf: - cf.write(cmd_text) - cmd_path = cf.name - - shell_cmd = ( - f'source "{args.settings}" >/dev/null 2>&1 && ' - f'"{impact}" -batch "{cmd_path}"' - ) - proc = subprocess.run(["bash", "-lc", shell_cmd], text=True, capture_output=True) - output = (proc.stdout or "") + (proc.stderr or "") - print(output, end="") - return proc.returncode - finally: - if cmd_path is not None: - try: - os.unlink(cmd_path) - except OSError: - pass - if svf_path is not None: - try: - os.unlink(svf_path) - except OSError: - pass - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tools/.gitignore b/tools/.gitignore new file mode 100644 index 0000000..c605dda --- /dev/null +++ b/tools/.gitignore @@ -0,0 +1,2 @@ +*.o +test \ No newline at end of file diff --git a/tools/Makefile b/tools/Makefile new file mode 100644 index 0000000..6e30c9e --- /dev/null +++ b/tools/Makefile @@ -0,0 +1,32 @@ +TOOLCHAIN_PREFIX ?= + +CC := $(TOOLCHAIN_PREFIX)g++ + +TARGET := test +SRCS_C := +SRCS_CPP:= test.cpp digilent_jtag.cpp +OBJS := $(SRCS_C:.c=.o) $(SRCS_CPP:.cpp=.o) + +ADEPT_LIBDIR := /opt/packages/digilent.adept.runtime_2.27.9-x86_64/lib64 + +CFLAGS := +ASFLAGS := +LDFLAGS := -L$(ADEPT_LIBDIR) -Wl,--disable-new-dtags -Wl,-rpath,$(ADEPT_LIBDIR) +LIBS := -ldjtg -ldmgr -ldpcomm -ldabs -ldftd2xx + + +.PHONY: all clean size + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +%.o: %.cpp + $(CC) $(CFLAGS) -c -o $@ $< + +clean: + rm -f $(TARGET) $(OBJS) \ No newline at end of file diff --git a/tools/digilent_jtag.cpp b/tools/digilent_jtag.cpp new file mode 100644 index 0000000..4de2307 --- /dev/null +++ b/tools/digilent_jtag.cpp @@ -0,0 +1,276 @@ +#include "digilent_jtag.hpp" + +#include +#include + +#include +#include + +namespace { + +constexpr int kDefaultIrBits = 6; + +std::string ercToString(ERC erc) { + char code[cchErcMax] = {0}; + char msg[cchErcMsgMax] = {0}; + if (DmgrSzFromErc(erc, code, msg)) { + return std::string(code) + ": " + msg; + } + return "ERC=" + std::to_string(erc); +} + +inline uint8_t getBit(const uint8_t* packed_bits, int bit_idx) { + return static_cast((packed_bits[bit_idx / 8] >> (bit_idx % 8)) & 0x1u); +} + +inline void setBit(uint8_t* packed_bits, int bit_idx, uint8_t bit) { + const uint8_t mask = static_cast(1u << (bit_idx % 8)); + if (bit & 0x1u) { + packed_bits[bit_idx / 8] |= mask; + } else { + packed_bits[bit_idx / 8] &= static_cast(~mask); + } +} + +} // namespace + +DigilentJtag::DigilentJtag() : hif_(hifInvalid), enabled_port_(-1), last_error_() {} + +DigilentJtag::~DigilentJtag() { close(); } + +bool DigilentJtag::open(int port) { + close(); + + int count = 0; + if (!DmgrEnumDevices(&count)) { + return setErrorFromDmgr("DmgrEnumDevices"); + } + if (count <= 0) { + return setError("open: no Digilent devices found"); + } + + DVC dvc{}; + if (!DmgrGetDvc(0, &dvc)) { + return setErrorFromDmgr("DmgrGetDvc"); + } + + return open(std::string(dvc.szConn), port); +} + +bool DigilentJtag::open(const std::string& selector, int port) { + close(); + + if (selector.empty()) { + return setError("open: selector is empty"); + } + + std::vector sel(selector.begin(), selector.end()); + sel.push_back('\0'); + + if (!DmgrOpen(&hif_, sel.data())) { + hif_ = hifInvalid; + return setErrorFromDmgr("DmgrOpen"); + } + + if (!DjtgEnableEx(hif_, static_cast(port))) { + if (!DjtgEnable(hif_)) { + DmgrClose(hif_); + hif_ = hifInvalid; + return setErrorFromDmgr("DjtgEnableEx/DjtgEnable"); + } + enabled_port_ = 0; + } else { + enabled_port_ = port; + } + + last_error_.clear(); + return true; +} + +void DigilentJtag::close() { + if (hif_ != hifInvalid) { + (void)DjtgDisable(hif_); + (void)DmgrClose(hif_); + } + hif_ = hifInvalid; + enabled_port_ = -1; +} + +bool DigilentJtag::isOpen() const { return hif_ != hifInvalid; } + +HIF DigilentJtag::handle() const { return hif_; } + +bool DigilentJtag::setSpeed(uint32_t requested_hz, uint32_t* actual_hz) { + if (!isOpen()) { + return setError("setSpeed: device not open"); + } + + DWORD actual = 0; + if (!DjtgSetSpeed(hif_, static_cast(requested_hz), &actual)) { + return setErrorFromDmgr("DjtgSetSpeed"); + } + if (actual_hz) { + *actual_hz = static_cast(actual); + } + last_error_.clear(); + return true; +} + +bool DigilentJtag::setChain(int chain, int ir_bits) { + uint32_t opcode = 0; + if (chain == 1) { + opcode = 0x02; // USER1 on Spartan-6 + } else if (chain == 2) { + opcode = 0x03; // USER2 on Spartan-6 + } else { + return setError("setChain: unsupported chain index (expected 1 or 2)"); + } + return setInstruction(opcode, ir_bits); +} + +bool DigilentJtag::setInstruction(uint32_t instruction, int ir_bits) { + if (!isOpen()) { + return setError("setInstruction: device not open"); + } + if (ir_bits <= 0 || ir_bits > 64) { + return setError("setInstruction: ir_bits out of range"); + } + + // Force Test-Logic-Reset, then RTI. + uint8_t tlr = 0x3f; // 6 ones + if (!putTmsBits(&tlr, 6)) return false; + uint8_t rti = 0x00; // one zero + if (!putTmsBits(&rti, 1)) return false; + + // RTI -> Shift-IR : 1,1,0,0 (LSB-first 0b0011) + uint8_t to_shift_ir = 0x03; + if (!putTmsBits(&to_shift_ir, 4)) return false; + + std::vector tx(static_cast((ir_bits + 7) / 8), 0); + for (int i = 0; i < ir_bits && i < 64; ++i) { + if ((instruction >> i) & 0x1u) { + tx[static_cast(i / 8)] |= static_cast(1u << (i % 8)); + } + } + std::vector rx(tx.size(), 0); + + if (ir_bits > 1) { + if (!putTdiBits(false, tx.data(), rx.data(), ir_bits - 1)) return false; + } + const uint8_t last_tx = getBit(tx.data(), ir_bits - 1); + uint8_t last_rx = 0; + if (!putTdiBits(true, &last_tx, &last_rx, 1)) return false; + + // Exit1-IR -> Update-IR -> Idle: 1,0 + uint8_t to_idle = 0x01; + if (!putTmsBits(&to_idle, 2)) return false; + + last_error_.clear(); + return true; +} + +bool DigilentJtag::shiftData(const uint8_t* tx_bits, uint8_t* rx_bits, int bit_count) { + if (!isOpen()) { + return setError("shiftData: device not open"); + } + if (bit_count <= 0) { + return setError("shiftData: bit_count must be > 0"); + } + + const size_t nbytes = static_cast((bit_count + 7) / 8); + std::vector tx_zeros; + if (!tx_bits) { + tx_zeros.assign(nbytes, 0); + tx_bits = tx_zeros.data(); + } + + std::vector rx_tmp; + if (!rx_bits) { + rx_tmp.assign(nbytes, 0); + rx_bits = rx_tmp.data(); + } else { + std::memset(rx_bits, 0, nbytes); + } + + if (!enterShiftDR()) return false; + + if (bit_count > 1) { + if (!putTdiBits(false, tx_bits, rx_bits, bit_count - 1)) return false; + } + + const uint8_t tx_last = getBit(tx_bits, bit_count - 1); + uint8_t rx_last = 0; + if (!putTdiBits(true, &tx_last, &rx_last, 1)) return false; + setBit(rx_bits, bit_count - 1, rx_last & 0x1u); + + if (!leaveShiftToIdle()) return false; + + last_error_.clear(); + return true; +} + +bool DigilentJtag::shiftData(const std::vector& tx_bits, std::vector* rx_bits, int bit_count) { + if (bit_count <= 0) { + return setError("shiftData(vector): bit_count must be > 0"); + } + const size_t nbytes = static_cast((bit_count + 7) / 8); + if (tx_bits.size() < nbytes) { + return setError("shiftData(vector): tx_bits is smaller than required bit_count"); + } + + std::vector local_rx; + local_rx.assign(nbytes, 0); + + if (!shiftData(tx_bits.data(), local_rx.data(), bit_count)) { + return false; + } + + if (rx_bits) { + *rx_bits = std::move(local_rx); + } + return true; +} + +const std::string& DigilentJtag::lastError() const { return last_error_; } + +bool DigilentJtag::enterShiftDR() { + // Idle -> Select-DR -> Capture-DR -> Shift-DR : 1,0,0 + uint8_t to_shift_dr = 0x01; + return putTmsBits(&to_shift_dr, 3); +} + +bool DigilentJtag::leaveShiftToIdle() { + // Exit1-DR -> Update-DR -> Idle : 1,0 + uint8_t to_idle = 0x01; + return putTmsBits(&to_idle, 2); +} + +bool DigilentJtag::putTmsBits(const uint8_t* tms_bits, int bit_count) { + if (!DjtgPutTmsBits(hif_, fFalse, const_cast(tms_bits), nullptr, static_cast(bit_count), fFalse)) { + return setErrorFromDmgr("DjtgPutTmsBits"); + } + return true; +} + +bool DigilentJtag::putTdiBits(bool tms, const uint8_t* tx_bits, uint8_t* rx_bits, int bit_count) { + if (!DjtgPutTdiBits( + hif_, + tms ? fTrue : fFalse, + const_cast(tx_bits), + rx_bits, + static_cast(bit_count), + fFalse)) { + return setErrorFromDmgr("DjtgPutTdiBits"); + } + return true; +} + +bool DigilentJtag::setError(const std::string& msg) { + last_error_ = msg; + return false; +} + +bool DigilentJtag::setErrorFromDmgr(const std::string& where) { + last_error_ = where + " failed: " + ercToString(DmgrGetLastError()); + return false; +} diff --git a/tools/digilent_jtag.hpp b/tools/digilent_jtag.hpp new file mode 100644 index 0000000..e792722 --- /dev/null +++ b/tools/digilent_jtag.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +#include + +class DigilentJtag { +public: + DigilentJtag(); + ~DigilentJtag(); + + DigilentJtag(const DigilentJtag&) = delete; + DigilentJtag& operator=(const DigilentJtag&) = delete; + + bool open(int port = 0); + bool open(const std::string& selector, int port = 0); + void close(); + + bool isOpen() const; + HIF handle() const; + + bool setSpeed(uint32_t requested_hz, uint32_t* actual_hz = nullptr); + + // For Spartan-6 style USER chains: + // chain=1 -> USER1 opcode 0x02, chain=2 -> USER2 opcode 0x03 + bool setChain(int chain, int ir_bits = 6); + + bool setInstruction(uint32_t instruction, int ir_bits); + + // Shifts one DR transaction from Idle -> ShiftDR -> UpdateDR -> Idle. + // tx_bits is LSB-first in packed byte form. + // rx_bits, when non-null, receives captured TDO bits in the same packing. + bool shiftData(const uint8_t* tx_bits, uint8_t* rx_bits, int bit_count); + + bool shiftData(const std::vector& tx_bits, std::vector* rx_bits, int bit_count); + + const std::string& lastError() const; + +private: + bool enterShiftDR(); + bool leaveShiftToIdle(); + bool putTmsBits(const uint8_t* tms_bits, int bit_count); + bool putTdiBits(bool tms, const uint8_t* tx_bits, uint8_t* rx_bits, int bit_count); + bool setError(const std::string& msg); + bool setErrorFromDmgr(const std::string& where); + + HIF hif_; + int enabled_port_; + std::string last_error_; +}; diff --git a/tools/test.cpp b/tools/test.cpp new file mode 100644 index 0000000..27f48c1 --- /dev/null +++ b/tools/test.cpp @@ -0,0 +1,19 @@ +#include "digilent_jtag.hpp" + +int main(int argc, char** argv){ + + DigilentJtag jtag; + if(!jtag.open()){ + printf("Could not open programmer\r\n"); + return -1; + } + + jtag.setChain(1); + + uint8_t data_out = 0xAB; + uint8_t data_in; + jtag.shiftData(&data_out, &data_in, 8); + + jtag.close(); + return 0; +} \ No newline at end of file