|
@@ -0,0 +1,374 @@
|
|
|
+//
|
|
|
+// vjtag_max80.sv
|
|
|
+//
|
|
|
+// Access the SDRAM and allow the CPU to take interrupts and receive
|
|
|
+// a command opcode via virtual JTAG.
|
|
|
+//
|
|
|
+// The module supports the following command codes over virtual JTAG:
|
|
|
+//
|
|
|
+// These chains use a common 1-bit boolean register:
|
|
|
+//
|
|
|
+// 00000 - bypass
|
|
|
+// 00010 - trigger IRQ on update_IR
|
|
|
+// 00100 - trigger system (soft) reset on update_IR
|
|
|
+// 0011x - assert halt while bit set
|
|
|
+// 0100x - memory underrun status flag, clear on capture
|
|
|
+//
|
|
|
+// These chains use a common 32-bit shift register:
|
|
|
+//
|
|
|
+// 1001x - address register (only bits [24:2] settable)
|
|
|
+// 1010x - read data from memory
|
|
|
+// 1011x - write data to memory
|
|
|
+// 1100x - command register to CPU
|
|
|
+// 1101x - command register to CPU, trigger IRQ on update_DR
|
|
|
+// 1110x - info register from CPU
|
|
|
+// 1111x - status register from CPU, clear on capture
|
|
|
+//
|
|
|
+// Setting bit 0 suppresses all register updates (but not other
|
|
|
+// side effects), allowing for nondestructive reads.
|
|
|
+//
|
|
|
+// The CPU registers are:
|
|
|
+//
|
|
|
+// 0 - CPU command register (readonly)
|
|
|
+// 1 - CPU information register (rw)
|
|
|
+// 2 - CPU status register (rw)
|
|
|
+// 3 - CPU status register set bits (rw1)
|
|
|
+//
|
|
|
+module vjtag_max80
|
|
|
+#(
|
|
|
+ parameter [31:0] sdram_base_addr,
|
|
|
+ parameter sdram_bits
|
|
|
+) (
|
|
|
+ input rst_n,
|
|
|
+ input sys_clk,
|
|
|
+
|
|
|
+ dram_bus.dstr sdram,
|
|
|
+
|
|
|
+ input cpu_valid,
|
|
|
+ input [6:2] cpu_addr,
|
|
|
+ input [31:0] cpu_wdata,
|
|
|
+ input [ 3:0] cpu_wstrb,
|
|
|
+ output [31:0] cpu_rdata,
|
|
|
+ output cpu_irq,
|
|
|
+ output cpu_halt,
|
|
|
+
|
|
|
+ output reg reset_cmd
|
|
|
+ );
|
|
|
+
|
|
|
+ wire v_tdi;
|
|
|
+ wire v_tdo;
|
|
|
+ wire [4:0] v_ir;
|
|
|
+ wire v_tck;
|
|
|
+ wire v_st_cdr;
|
|
|
+ wire v_st_sdr;
|
|
|
+ wire v_st_e1dr;
|
|
|
+ wire v_st_pdr;
|
|
|
+ wire v_st_e2dr;
|
|
|
+ wire v_st_udr;
|
|
|
+ wire v_st_cir;
|
|
|
+ wire v_st_uir;
|
|
|
+
|
|
|
+ vjtag vjtag (
|
|
|
+ .tdi ( v_tdi ),
|
|
|
+ .tdo ( v_tdo ),
|
|
|
+ .tck ( v_tck ), // Really nothing virtual...
|
|
|
+ .ir_in ( v_ir ),
|
|
|
+ .ir_out ( ),
|
|
|
+ .virtual_state_cdr ( v_st_cdr ),
|
|
|
+ .virtual_state_e1dr ( v_st_e1dr ),
|
|
|
+ .virtual_state_e2dr ( v_st_e2dr ),
|
|
|
+ .virtual_state_pdr ( v_st_pdr ),
|
|
|
+ .virtual_state_sdr ( v_st_sdr ),
|
|
|
+ .virtual_state_udr ( v_st_udr ),
|
|
|
+ .virtual_state_uir ( v_st_uir ),
|
|
|
+ .virtual_state_cir ( v_st_cir )
|
|
|
+ );
|
|
|
+
|
|
|
+ localparam cmd_bypass = 4'b0000;
|
|
|
+ localparam cmd_irq = 4'b0001;
|
|
|
+ localparam cmd_reset = 4'b0010;
|
|
|
+ localparam cmd_halt = 4'b0011;
|
|
|
+ localparam cmd_memerr = 4'b0100;
|
|
|
+
|
|
|
+ localparam cmd_mem0 = 4'b1000;
|
|
|
+ localparam cmd_memaddr = 4'b1001;
|
|
|
+ localparam cmd_memread = 4'b1010;
|
|
|
+ localparam cmd_memwrite = 4'b1011;
|
|
|
+ localparam cmd_memwr = 3'b101; // Common bits of read and write
|
|
|
+ localparam cmd_cpucmd = 4'b1100;
|
|
|
+ localparam cmd_cpucmd_irq = 4'b1101; // cpucmd but trigger IRQ
|
|
|
+ localparam cmd_cpuinfo = 4'b1110;
|
|
|
+ localparam cmd_cpustatus = 4'b1111;
|
|
|
+
|
|
|
+ reg jtag_bypass;
|
|
|
+ reg [31:0] jtag_shr;
|
|
|
+ reg tdi_s;
|
|
|
+
|
|
|
+ // Latched information for use in the synchronous state machine
|
|
|
+ reg [3:0] ir_cmd; // Command part of IR
|
|
|
+ reg ir_ro; // Readonly (update suppress)
|
|
|
+ reg st_cdr_s;
|
|
|
+ reg st_sdr_s;
|
|
|
+ reg st_xdr_s; // Any state between CDR and UDR, exclusively
|
|
|
+ reg st_udr_s;
|
|
|
+ reg st_uir_s;
|
|
|
+
|
|
|
+ always @(posedge v_tck)
|
|
|
+ begin
|
|
|
+ jtag_bypass <= v_ir == cmd_bypass;
|
|
|
+ tdi_s <= v_tdi;
|
|
|
+ ir_cmd <= v_ir[4:1];
|
|
|
+ ir_ro <= v_ir[0];
|
|
|
+ st_cdr_s <= v_st_cdr;
|
|
|
+ st_sdr_s <= v_st_sdr;
|
|
|
+ st_xdr_s <= v_st_sdr|v_st_e1dr|v_st_e2dr|v_st_pdr;
|
|
|
+ st_udr_s <= v_st_udr;
|
|
|
+ st_uir_s <= v_st_uir;
|
|
|
+ end
|
|
|
+
|
|
|
+ assign v_tdo = jtag_bypass ? tdi_s : jtag_shr[0];
|
|
|
+
|
|
|
+ // Sync incoming JTAG signals. Only tck needs an actual
|
|
|
+ // synchronizer; the rest just need holding registers (see above)
|
|
|
+ // as the delay of tck will guarantee the others are stable.
|
|
|
+ wire tck_s;
|
|
|
+ synchronizer #(.width(1)) tck_sync
|
|
|
+ (
|
|
|
+ .rst_n ( rst_n ),
|
|
|
+ .clk ( sys_clk ),
|
|
|
+ .d ( v_tck ),
|
|
|
+ .q ( tck_s )
|
|
|
+ );
|
|
|
+
|
|
|
+ // Mask of memaddr bits that are not settable: the top bits
|
|
|
+ // and the bottom two bits (byte within dword)
|
|
|
+ localparam [31:0] memaddr_mask = (1'b1 << sdram_bits) - 3'b100;
|
|
|
+
|
|
|
+ function logic [31:0] maskaddr (input [31:0] addr);
|
|
|
+ maskaddr = (addr & memaddr_mask) | sdram_base_addr;
|
|
|
+ endfunction
|
|
|
+
|
|
|
+ reg jtag_cpu_irq = 1'b0;
|
|
|
+ reg jtag_cpu_halt = 1'b0;
|
|
|
+ reg jtag_reset_cmd = 1'b0;
|
|
|
+
|
|
|
+ assign cpu_irq = jtag_cpu_irq;
|
|
|
+ assign cpu_halt = jtag_cpu_halt;
|
|
|
+ assign reset_cmd = jtag_reset_cmd;
|
|
|
+
|
|
|
+ reg [31:0] jtag_memaddr = maskaddr(32'b0);
|
|
|
+ reg [31:0] jtag_cpucmd;
|
|
|
+ reg [31:0] jtag_cpuinfo;
|
|
|
+ reg [ 7:0] jtag_cpustatus;
|
|
|
+
|
|
|
+ reg mem_valid;
|
|
|
+ reg mem_write;
|
|
|
+ reg [31:0] mem_addr;
|
|
|
+ wire [31:0] mem_addr_next = maskaddr(mem_addr + 3'h4);
|
|
|
+ wire [31:0] mem_rdata;
|
|
|
+ reg [31:0] mem_wdata;
|
|
|
+ wire mem_ready;
|
|
|
+ reg mem_done;
|
|
|
+ reg mem_error; // Memory underrun
|
|
|
+
|
|
|
+ reg tck_q;
|
|
|
+ reg tck_stb;
|
|
|
+ always @(posedge sys_clk)
|
|
|
+ begin
|
|
|
+ tck_q <= tck_s;
|
|
|
+ tck_stb <= tck_s & ~tck_q;
|
|
|
+ end
|
|
|
+
|
|
|
+ // Keep a counter to keep track of SDRAM data bit count; this is to
|
|
|
+ // allow streaming of data to/from SDRAM without leaving the
|
|
|
+ // SDR state.
|
|
|
+ reg [4:0] sdr_ctr;
|
|
|
+
|
|
|
+ always @(posedge sys_clk)
|
|
|
+ begin
|
|
|
+ if ( ~rst_n )
|
|
|
+ jtag_reset_cmd <= 1'b0;
|
|
|
+
|
|
|
+ jtag_cpu_irq <= 1'b0;
|
|
|
+
|
|
|
+ if ( tck_stb )
|
|
|
+ begin
|
|
|
+ if ( st_sdr_s )
|
|
|
+ begin
|
|
|
+ if ( ir_cmd[3] )
|
|
|
+ jtag_shr <= { tdi_s, jtag_shr[31:1] };
|
|
|
+ else
|
|
|
+ jtag_shr <= { 30'bx, tdi_s, jtag_shr[1] };
|
|
|
+ end
|
|
|
+
|
|
|
+ if ( st_cdr_s )
|
|
|
+ case ( ir_cmd )
|
|
|
+ cmd_halt: jtag_shr[0] <= jtag_cpu_halt;
|
|
|
+ cmd_memerr: begin
|
|
|
+ jtag_shr[0] <= mem_error;
|
|
|
+ if ( ~ir_ro ) mem_error <= 1'b0;
|
|
|
+ end
|
|
|
+ cmd_mem0, cmd_memaddr, cmd_memread, cmd_memwrite:
|
|
|
+ jtag_shr <= jtag_memaddr;
|
|
|
+ cmd_cpucmd, cmd_cpucmd_irq:
|
|
|
+ jtag_shr <= jtag_cpucmd;
|
|
|
+ cmd_cpuinfo: jtag_shr <= jtag_cpuinfo;
|
|
|
+ cmd_cpustatus: jtag_shr <= jtag_cpustatus;
|
|
|
+ default: ;
|
|
|
+ endcase // case ( ir_cmd )
|
|
|
+
|
|
|
+ // For performance, the SDRAM data can be streamed
|
|
|
+ // without exiting the shift_DR state, so this is
|
|
|
+ // based on a counter rather than going to the DR_update
|
|
|
+ // state.
|
|
|
+ //
|
|
|
+ // Note: for cmd_memread, this will trigger an
|
|
|
+ // unnecessary memory load at the end, but that is
|
|
|
+ // completely harmless, and we cannot know until the clock
|
|
|
+ // comes in if we need it or not, so we have to
|
|
|
+ // suppress the update until we know that the user will
|
|
|
+ // be reading the fetched data.
|
|
|
+
|
|
|
+ if ( ir_cmd[3:1] == cmd_memwr )
|
|
|
+ begin
|
|
|
+ if ( st_cdr_s )
|
|
|
+ begin
|
|
|
+ mem_done <= 1'b0;
|
|
|
+ mem_valid <= 1'b0;
|
|
|
+ mem_write <= ir_cmd[0];
|
|
|
+ end
|
|
|
+
|
|
|
+ if ( ~st_xdr_s )
|
|
|
+ begin
|
|
|
+ sdr_ctr <= 5'b0;
|
|
|
+ mem_addr <= jtag_memaddr;
|
|
|
+ end
|
|
|
+
|
|
|
+ if ( st_sdr_s )
|
|
|
+ begin
|
|
|
+ sdr_ctr <= sdr_ctr + 1'b1;
|
|
|
+
|
|
|
+ // Applicable to both read and write
|
|
|
+ case ( sdr_ctr )
|
|
|
+ 5'd31: begin
|
|
|
+ if ( mem_valid )
|
|
|
+ mem_error <= 1'b1; // Underrun!
|
|
|
+ end
|
|
|
+ default: ;
|
|
|
+ endcase // case ( sdr_ctr )
|
|
|
+
|
|
|
+ if ( ~mem_write )
|
|
|
+ // Read
|
|
|
+ case ( sdr_ctr )
|
|
|
+ 5'd1: begin
|
|
|
+ // For a read, make sure we are committed
|
|
|
+ // to reading the new word
|
|
|
+ if ( ~ir_ro )
|
|
|
+ jtag_memaddr <= mem_addr;
|
|
|
+ mem_valid <= 1'b1;
|
|
|
+ mem_done <= 1'b0;
|
|
|
+ end
|
|
|
+ 5'd31: begin
|
|
|
+ jtag_shr <= mem_rdata;
|
|
|
+ end
|
|
|
+ default: ;
|
|
|
+ endcase // case ( sdr_ctr )
|
|
|
+ else
|
|
|
+ // Write
|
|
|
+ case ( sdr_ctr )
|
|
|
+ 5'd31: begin
|
|
|
+ mem_wdata <= jtag_shr;
|
|
|
+ mem_valid <= 1'b1;
|
|
|
+ mem_done <= 1'b0;
|
|
|
+ if ( ~ir_ro )
|
|
|
+ jtag_memaddr <= mem_addr_next;
|
|
|
+ end
|
|
|
+ default: ;
|
|
|
+ endcase // case ( sdr_ctr )
|
|
|
+ end // if ( st_sdr_s )
|
|
|
+ end // if ( ir_cmd[3:1] == cmd_memwr )
|
|
|
+
|
|
|
+ if ( st_uir_s )
|
|
|
+ jtag_cpu_irq <= ir_cmd == cmd_irq;
|
|
|
+ else if ( st_udr_s )
|
|
|
+ jtag_cpu_irq <= ir_cmd == cmd_cpucmd_irq;
|
|
|
+
|
|
|
+ if ( st_uir_s )
|
|
|
+ jtag_reset_cmd <= jtag_reset_cmd | (ir_cmd == cmd_reset);
|
|
|
+
|
|
|
+ if ( st_udr_s & ~ir_ro )
|
|
|
+ case ( ir_cmd )
|
|
|
+ cmd_halt: jtag_cpu_halt <= jtag_shr[0];
|
|
|
+ cmd_memaddr: jtag_memaddr <= maskaddr(jtag_shr);
|
|
|
+ cmd_cpucmd, cmd_cpucmd_irq:
|
|
|
+ jtag_cpucmd <= jtag_shr;
|
|
|
+ default: /* nothing */ ;
|
|
|
+ endcase // case ( ir_cmd )
|
|
|
+ end // if ( tck_stb )
|
|
|
+
|
|
|
+ // Increment the temporary address register if applicable,
|
|
|
+ // but only after the previous transaction is done...
|
|
|
+ if ( mem_valid & mem_ready )
|
|
|
+ begin
|
|
|
+ mem_valid <= 1'b0;
|
|
|
+ mem_done <= 1'b1;
|
|
|
+ mem_addr <= mem_addr_next;
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ dram_port #(32) mem
|
|
|
+ (
|
|
|
+ .bus ( sdram ),
|
|
|
+ .prio ( 2'd2 ),
|
|
|
+ .addr ( mem_addr ),
|
|
|
+ .valid ( mem_valid ),
|
|
|
+ .wd ( mem_wdata ),
|
|
|
+ .wstrb ( {4{mem_write}} ),
|
|
|
+ .ready ( mem_ready ),
|
|
|
+ .rd ( mem_rdata )
|
|
|
+ );
|
|
|
+
|
|
|
+ wire [7:0] cpustatus_new =
|
|
|
+ ( tck_stb & st_cdr_s & ~ir_ro & (ir_cmd == cmd_cpustatus) )
|
|
|
+ ? 'b0 : jtag_cpustatus;
|
|
|
+
|
|
|
+ always @(negedge rst_n or posedge sys_clk)
|
|
|
+ if (~rst_n)
|
|
|
+ begin
|
|
|
+ jtag_cpuinfo <= 'b0;
|
|
|
+ jtag_cpustatus <= 'b0;
|
|
|
+ end
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ jtag_cpustatus <= cpustatus_new;
|
|
|
+
|
|
|
+ if ( cpu_valid )
|
|
|
+ begin
|
|
|
+ case ( cpu_addr )
|
|
|
+ 5'b00001: begin
|
|
|
+ if ( cpu_wstrb[0] ) jtag_cpuinfo[7:0] <= cpu_wdata[7:0];
|
|
|
+ if ( cpu_wstrb[1] ) jtag_cpuinfo[15:8] <= cpu_wdata[15:8];
|
|
|
+ if ( cpu_wstrb[2] ) jtag_cpuinfo[23:16] <= cpu_wdata[23:16];
|
|
|
+ if ( cpu_wstrb[3] ) jtag_cpuinfo[31:24] <= cpu_wdata[31:24];
|
|
|
+ end
|
|
|
+ 5'b00010: begin
|
|
|
+ if ( cpu_wstrb[0] ) jtag_cpustatus <= cpu_wdata[7:0];
|
|
|
+ // Add more if the width of jtag_cpustatus is increased
|
|
|
+ end
|
|
|
+ 5'b00011: begin
|
|
|
+ if ( cpu_wstrb[0] )
|
|
|
+ jtag_cpustatus <= cpustatus_new | cpu_wdata[7:0];
|
|
|
+ end
|
|
|
+ default: /* nothing */;
|
|
|
+ endcase // case ( cpu_addr[1:0] )
|
|
|
+ end // if ( cpu_valid )
|
|
|
+ end
|
|
|
+
|
|
|
+ always @(*)
|
|
|
+ casez ( cpu_addr )
|
|
|
+ 5'b00000: cpu_rdata = jtag_cpucmd;
|
|
|
+ 5'b00001: cpu_rdata = jtag_cpuinfo;
|
|
|
+ 5'b0001?: cpu_rdata = jtag_cpustatus;
|
|
|
+ default: cpu_rdata = 32'bx;
|
|
|
+ endcase // casez ( cpu_addr )
|
|
|
+endmodule // vjtag_max80
|