// // File: jtag2mem.v // Date: 09-Dec-05 // Author: I. Chuang // // Verilog module to communicate between host PC and Xilinx FPGA using // jtag interface. Allows memory contents to be read out and written to // by the PC. // // The JTAG interface can read/write two user registers in the FPGA, // USER1 and USER2. We use USER1 as a four-bit instruction register, // and USER2 as a variable bit width data register. See the code below // for the various instructions. The basic interaction model is a memory // interface. A counter provides the address and data reads and writes // automatically increment the counter. The counter can also be read // from and written to, and initialized to zero. // // This code is part of the jtag2mem package, created for educational // purposes for the MIT 6.111 Digital Systems Laboratory course (fall'05). // //---------------------------------------------------------------------------- // // This file is part of jtag2mem, (c) 2005 Isaac Chuang // // jtag2mem is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation; either version 2 of the License, or (at your // option) any later version. // // jtag2mem is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this code; if not, write to the Free Software Foundation, // Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // //---------------------------------------------------------------------------- module jtag2mem #( parameter DBITS = 8, parameter ABITS = 10 ) ( input wire reset, // system reset input wire clk, // system clock input wire [DBITS-1:0] read_data, // memory read data output reg [DBITS-1:0] write_data, // memory write data output reg [ABITS-1:0] ram_addr, // memory address output reg ram_we, // memory write-enable (active high) output wire [3:0] ctrl_state // for debugging ); //////////////////////////////////////////////////////////// // instantiate low-level JTAG interface module wire jtagCAPTURE; wire jtagDRCK1; wire jtagDRCK2; wire jtagRESET; wire jtagSEL1; wire jtagSEL2; wire jtagSHIFT; wire jtagTDI; wire jtagUPDATE; wire jtagTDO1; wire jtagTDO2; BSCAN_VIRTEX2 BSCAN_VIRTEX2_inst ( .CAPTURE(jtagCAPTURE), // CAPTURE output from TAP controller .DRCK1(jtagDRCK1), // Data register output - USER1 functions .DRCK2(jtagDRCK2), // Data register output - USER2 functions .RESET(jtagRESET), // Reset output from TAP controller .SEL1(jtagSEL1), // USER1 active output .SEL2(jtagSEL2), // USER2 active output .SHIFT(jtagSHIFT), // SHIFT output from TAP controller .TDI(jtagTDI), // TDI output from TAP controller .UPDATE(jtagUPDATE), // UPDATE output from TAP controller .TDO1(jtagTDO1), // Data input for USER1 function .TDO2(jtagTDO2) // Data input for USER2 function ); //////////////////////////////////////////////////////////// // User register 1 is for instructions and control. // User register 2 is for data. // // The protocol is to receive one or more instructions to control // the address counter; for each data byte received / requested, // the memory address counter is incremented. When in write-data // mode, data is written to the current address before the address // is incremented. Data is read from each memory location and // returned through the JTAG port on TDO on every data register read. // // Instructions (4 bits): // // 0 = read data mode // 1 = zero the memory address counter // 2 = load the memory address counter from the next data reg load // 3 = read the memory address counter on the next data reg read // 4 = write-data mode (enable writes) // 5 = read checksum // // Note that we bit-shift in reverse of some JTAG conventions, so // that the xtclsh scripts bit strings read exactly as you // would expect (msb on left, lsb on right). // // Also note that after requesting the memory address counter to be // zero'ed, the data register should be accessed once to execute // this request. The memory address counter is changed only after // the data register is updated. // // The checksum is computed as the DBITS-bit sum of all the data bits // transferred since the last time the memory address counter was // zero'ed or loaded. As of 10-Dec-05, the checksum code is not yet // implemented. localparam CMD_READD = 0; // read data localparam CMD_ZRADR = 1; localparam CMD_LDADR = 2; localparam CMD_RDADR = 3; localparam CMD_WRDAT = 4; reg [3:0] dr1; // user data register 1 wire [3:0] outdat1; // data to output in dr1 always @(posedge jtagDRCK1) dr1 <= jtagRESET ? 0 : jtagSHIFT ? {dr1[2:0],jtagTDI} // shift left : outdat1; assign jtagTDO1 = dr1[3]; reg [3:0] reg_ctrl; // control register always @(posedge jtagUPDATE) reg_ctrl <= jtagRESET ? 0 : jtagSEL1 ? dr1[3:0] : reg_ctrl; // decode some control lines wire data_adr_mux; // mux to DR2: 0 = mem data (wr), 1 = mem addr assign data_adr_mux = ( (reg_ctrl==CMD_RDADR) | (reg_ctrl==CMD_LDADR) ); // data register reg [63:0] dr2; // user data register 2 wire [63:0] outdat2; // data to output in dr2 reg [ABITS-1:0] my_addr; // current memory address reg [ABITS-1:0] last_addr; // last memory address always @(posedge jtagDRCK2) dr2 <= jtagRESET ? 0 : jtagSHIFT ? {dr2[62:0],jtagTDI} // shift left : outdat2; assign jtagTDO2 = data_adr_mux ? dr2[ABITS-1] : dr2[DBITS-1]; assign outdat2 = data_adr_mux ? {{(64-ABITS){1'b0}}, ram_addr} : {{(64-DBITS){1'b0}}, read_data}; reg [DBITS-1:0] reg_data; // data register reg new_data; // flag: flips whenever new data avail always @(posedge jtagUPDATE) begin reg_data <= ( jtagRESET ? 0 : (jtagSEL2 & (reg_ctrl==CMD_WRDAT)) ? dr2[DBITS-1:0] : reg_data ); my_addr <= ( (jtagSEL2 & (reg_ctrl==CMD_ZRADR)) ? 0 : (jtagSEL2 & (reg_ctrl==CMD_LDADR)) ? dr2[ABITS-1:0] : (jtagSEL2 & (reg_ctrl==CMD_RDADR)) ? my_addr // no incr : jtagSEL2 ? my_addr + 1 : my_addr ); last_addr <= jtagSEL2 ? my_addr : last_addr; new_data <= (jtagSEL2 & (reg_ctrl==CMD_WRDAT)) ? ~new_data : new_data; end // synchronize address, data, nd, and instruction lines to system clock reg [ABITS-1:0] a_s[3:0]; reg [ABITS-1:0] l_s[3:0]; reg [DBITS-1:0] d_s[3:0]; reg [3:0] c_s[3:0]; reg n_s[3:0]; always @(posedge clk) begin {a_s[3],a_s[2],a_s[1],a_s[0]} <= {a_s[2],a_s[1],a_s[0],my_addr}; {l_s[3],l_s[2],l_s[1],l_s[0]} <= {l_s[2],l_s[1],l_s[0],last_addr}; {d_s[3],d_s[2],d_s[1],d_s[0]} <= {d_s[2],d_s[1],d_s[0],reg_data}; {c_s[3],c_s[2],c_s[1],c_s[0]} <= {c_s[2],c_s[1],c_s[0],reg_ctrl}; {n_s[3],n_s[2],n_s[1],n_s[0]} <= {n_s[2],n_s[1],n_s[0],new_data}; end // Generate write enable signal for RAM // // Note on timing: we trigger a write when the address changes, but the // data is to be written to the _current_ address, not the next one. // Thus, the address lines are delayed; for stability, we // delay by two clock cycles. // // Timing diagram: // // Clk 1 2 3 4 5 6 7 // cur_addr a0 a0 a1 a1 a2 a2 a2 // next_addr a1 a1 a2 a2 a3 a3 a3 // the_data d0 d0 d1 d1 d2 d2 d2 // new 0 0 1 1 0 0 0 // the_we 0 0 1 0 1 0 0 // ram_we 0 0 0 1 0 1 0 // write_data x x x d1 d1 d2 d2 // ram_addr a1 a1 a1 a1 a2 a2 a3 // // This guarantees that the address and data will be stable before and // after a write, for extra robustness in interfacing to user memories. wire [ABITS-1:0] next_addr = a_s[3]; wire [ABITS-1:0] cur_addr = l_s[3]; wire [DBITS-1:0] the_data = d_s[3]; wire [3:0] the_ctrl = c_s[3]; reg old_ns; wire the_we = (old_ns != n_s[3]); always @(posedge clk) begin old_ns <= n_s[3]; ram_we <= the_we; ram_addr <= the_we ? cur_addr : next_addr; write_data <= the_we ? the_data : write_data; end // debugging assign ctrl_state = the_ctrl; endmodule