Start hacking on ECP5 JTAG DTM
This commit is contained in:
parent
41477ce479
commit
8ceae7e9e6
|
@ -0,0 +1,6 @@
|
|||
file hazard3_ecp5_jtag_dtm.v
|
||||
file hazard3_jtag_dtm_core.v
|
||||
|
||||
file ../cdc/hazard3_apb_async_bridge.v
|
||||
file ../cdc/hazard3_reset_sync.v
|
||||
file ../cdc/hazard3_sync_1bit.v
|
|
@ -0,0 +1,229 @@
|
|||
/**********************************************************************
|
||||
* DO WHAT THE FUCK YOU WANT TO AND DON'T BLAME US PUBLIC LICENSE *
|
||||
* Version 3, April 2008 *
|
||||
* *
|
||||
* Copyright (C) 2021 Luke Wren *
|
||||
* *
|
||||
* Everyone is permitted to copy and distribute verbatim or modified *
|
||||
* copies of this license document and accompanying software, and *
|
||||
* changing either is allowed. *
|
||||
* *
|
||||
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION *
|
||||
* *
|
||||
* 0. You just DO WHAT THE FUCK YOU WANT TO. *
|
||||
* 1. We're NOT RESPONSIBLE WHEN IT DOESN'T FUCKING WORK. *
|
||||
* *
|
||||
*********************************************************************/
|
||||
|
||||
// The ECP5 JTAGG primitive (yes that is the correct spelling) allows you to
|
||||
// add two custom DRs to the FPGA's chip TAP, selected using the 8-bit ER1
|
||||
// (0x32) and ER2 (0x38) instructions.
|
||||
//
|
||||
// Brian Swetland pointed out on Twitter that the standard RISC-V JTAG-DTM
|
||||
// only uses two DRs (DTMCS and DMI), besides the standard IDCODE and BYPASS
|
||||
// whic. This file instantiates the guts of Hazard3's standard JTAG-DTM and
|
||||
// connects the DTMCS and DMI registers to the JTAGG primitive's ER1/ER2 DRs.
|
||||
//
|
||||
// The exciting part is that upstream OpenOCD already allows you to set the IR
|
||||
// length *and* set custom DTMCS/DMI IR values for RISC-V JTAG DTMs. This
|
||||
// means with the right config file, you can access a debug module hung from
|
||||
// the ECP5 TAP in this fashion using only upstream OpenOCD and gdb.
|
||||
|
||||
`default_nettype none
|
||||
|
||||
module hazard3_ecp5_jtag_dtm #(
|
||||
parameter DTMCS_IDLE_HINT = 3'd4,
|
||||
parameter W_ADDR = 8
|
||||
) (
|
||||
// This is synchronous to TCK and asserted for one TCK cycle only
|
||||
output wire dmihardreset_req,
|
||||
|
||||
// Bus clock + reset for Debug Module Interface
|
||||
input wire clk_dmi,
|
||||
input wire rst_n_dmi,
|
||||
|
||||
// Debug Module Interface (APB)
|
||||
output wire dmi_psel,
|
||||
output wire dmi_penable,
|
||||
output wire dmi_pwrite,
|
||||
output wire [W_ADDR-1:0] dmi_paddr,
|
||||
output wire [31:0] dmi_pwdata,
|
||||
input wire [31:0] dmi_prdata,
|
||||
input wire dmi_pready,
|
||||
input wire dmi_pslverr
|
||||
);
|
||||
|
||||
// Signals to/from the ECP5 TAP
|
||||
|
||||
wire jtdo2;
|
||||
wire jtdo1;
|
||||
wire jtdi;
|
||||
wire jtck;
|
||||
wire jrti2;
|
||||
wire jrti1;
|
||||
wire jshift;
|
||||
wire jupdate;
|
||||
wire jrst_n;
|
||||
wire jce2;
|
||||
wire jce1;
|
||||
|
||||
JTAGG jtag_u (
|
||||
.JTDO2 (jtdo2),
|
||||
.JTDO1 (jtdo1),
|
||||
.JTDI (jtdi),
|
||||
.JTCK (jtck),
|
||||
.JRTI2 (jrti2),
|
||||
.JRTI1 (jrti1),
|
||||
.JSHIFT (jshift),
|
||||
.JUPDATE (jupdate),
|
||||
.JRSTN (jrst_n),
|
||||
.JCE2 (jce2),
|
||||
.JCE1 (jce1)
|
||||
);
|
||||
|
||||
localparam W_DR_SHIFT = W_ADDR + 32 + 2;
|
||||
|
||||
wire core_dr_wen;
|
||||
wire core_dr_ren;
|
||||
wire core_dr_sel_dmi_ndtmcs;
|
||||
wire [W_DR_SHIFT-1:0] core_dr_wdata;
|
||||
wire [W_DR_SHIFT-1:0] core_dr_rdata;
|
||||
|
||||
// We would like to know at all times which DR is selected. Unfortunately
|
||||
// JTAGG does not tell us this. Instead:
|
||||
//
|
||||
// - During run test/idle, jrti1/jrti2 is asserted if IR matches ER1/ER2
|
||||
//
|
||||
// - During CAPTURE OR SHIFT, jce1/jce2 is asserted if IR matches ER1/ER2
|
||||
//
|
||||
// There is no signal that is valid during UPDATE. So we make our own:
|
||||
|
||||
reg dr_sel_prev;
|
||||
assign core_dr_sel_dmi_ndtmcs = jce1 ? 1'b0 : jce2 ? 1'b1 : dr_sel_prev;
|
||||
|
||||
always @ (posedge jtck or negedge jrst_n) begin
|
||||
if (!jrst_n) begin
|
||||
dr_sel_prev <= 1'b0;
|
||||
end else begin
|
||||
dr_sel_prev <= core_dr_sel_dmi_ndtmcs;
|
||||
end
|
||||
end
|
||||
|
||||
// This is equivalent to "in capture DR state and IR is ER1 or ER2"
|
||||
assign core_dr_ren = (jce1 || jce2) && !jshift;
|
||||
|
||||
assign core_dr_wen = jupdate;
|
||||
|
||||
// Our DR shifter is made much more complex by the flop inserted by JTAGG
|
||||
// between TDI and JTDI, which we have no control of. Say we have a total DR
|
||||
// shift length of 42 (8 addr 32 data 2 op, in DMI) and first consider just
|
||||
// SHIFT -> UPDATE:
|
||||
//
|
||||
// - After 42 SHIFT clocks, the 42nd data bit will be in the JTDI register
|
||||
//
|
||||
// - When we UPDATE, the write data must be the concatenation of the JTDI
|
||||
// register and a 41 bit shift register which follows JTDI
|
||||
//
|
||||
// As we shift, JTDI plus 41 other flops form our 42 bit shift register. So
|
||||
// far, mostly normal. The problem is that when we CAPTURE, we can't put the
|
||||
// 42nd data bit into the JTDI register, because we have no control of it. We
|
||||
// can't have a chain of 42 FPGA flops, because then our total scan length
|
||||
// appears from the outside to be 43 bits. So the trick is:
|
||||
//
|
||||
// - The frontmost flop in the 42-bit scan is usually JTDI, but we have an
|
||||
// additional shadow flop that is used on the first SHIFT cycle after
|
||||
// CAPTURE
|
||||
//
|
||||
// - CAPTURE loads rdata into the shadow flop and the 41 regular shift flops
|
||||
//
|
||||
// - The first SHIFT clock drops the shifter LSB (which was previously on
|
||||
// TDO), clocks the shadow flop down into the 41st position (which would
|
||||
// normally take data from JTDI), and JTDI is swapped back in place of the
|
||||
// shadow flop for UPDATE purposes
|
||||
//
|
||||
// - We are now in steady-state SHIFT.
|
||||
//
|
||||
// So before/after the first SHIFT clock the notional 42-bit register is
|
||||
// {capture[41:0]} -> {JTDI reg, capture[41:1]} Where capture[41] is
|
||||
// initially stored in the shadow flop, and then passes on to flop 40 of the
|
||||
// main shift register. (we don't support zero-bit SHIFT, who cares!)
|
||||
//
|
||||
// Ok maybe that was a longwinded explanation but this really confused the
|
||||
// shit out of me, so this is a gift for future Luke or other readers
|
||||
|
||||
|
||||
reg dr_shift_head;
|
||||
reg [W_DR_SHIFT-2:0] dr_shift_tail;
|
||||
reg use_shift_head;
|
||||
|
||||
assign core_dr_wdata = core_dr_sel_dmi_ndtmcs ? {jtdi, dr_shift_tail} :
|
||||
{{W_DR_SHIFT-32{1'b0}}, jtdi, dr_shift_tail[30:0]};
|
||||
|
||||
always @ (posedge jtck or negedge jrst_n) begin
|
||||
if (!jrst_n) begin
|
||||
dr_shift_head <= 1'b0;
|
||||
dr_shift_tail <= {W_DR_SHIFT-1{1'b0}};
|
||||
use_shift_head <= 1'b0;
|
||||
end else if (core_dr_ren) begin
|
||||
use_shift_head <= 1'b1;
|
||||
{dr_shift_head, dr_shift_tail} <= core_dr_rdata;
|
||||
end else begin
|
||||
use_shift_head <= 1'b0;
|
||||
dr_shift_tail <= {
|
||||
use_shift_head ? dr_shift_head : jtdi,
|
||||
dr_shift_tail
|
||||
} >> 1;
|
||||
if (!core_dr_sel_dmi_ndtmcs)
|
||||
dr_shift_tail[30] <= jtdi;
|
||||
end
|
||||
end
|
||||
|
||||
// We have only a single shifter for the ER1 and ER2 chains, so these are tied
|
||||
// together:
|
||||
|
||||
reg shift_tail_neg;
|
||||
|
||||
always @ (negedge jtck or negedge jrst_n) begin
|
||||
if (!jrst_n) begin
|
||||
shift_tail_neg <= 1'b0;
|
||||
end else begin
|
||||
shift_tail_neg <= dr_shift_tail[0];
|
||||
end
|
||||
end
|
||||
|
||||
assign jtdo1 = shift_tail_neg;
|
||||
assign jtdo2 = shift_tail_neg;
|
||||
|
||||
// The actual DTM is in here:
|
||||
|
||||
// hazard3_jtag_dtm_core #(
|
||||
// .DTMCS_IDLE_HINT(DTMCS_IDLE_HINT),
|
||||
// .W_ADDR(W_ADDR),
|
||||
// .W_DR_SHIFT(W_DR_SHIFT)
|
||||
// ) inst_hazard3_jtag_dtm_core (
|
||||
// .tck (tck),
|
||||
// .trst_n (trst_n),
|
||||
// .clk_dmi (clk_dmi),
|
||||
// .rst_n_dmi (rst_n_dmi),
|
||||
|
||||
// .dr_wen (core_dr_wen),
|
||||
// .dr_ren (core_dr_ren),
|
||||
// .dr_sel_dmi_ndtmcs (core_dr_sel_dmi_ndtmcs),
|
||||
// .dr_wdata (core_dr_wdata),
|
||||
// .dr_rdata (core_dr_rdata),
|
||||
|
||||
// .dmihardreset_req (dmihardreset_req),
|
||||
|
||||
// .dmi_psel (dmi_psel),
|
||||
// .dmi_penable (dmi_penable),
|
||||
// .dmi_pwrite (dmi_pwrite),
|
||||
// .dmi_paddr (dmi_paddr),
|
||||
// .dmi_pwdata (dmi_pwdata),
|
||||
// .dmi_prdata (dmi_prdata),
|
||||
// .dmi_pready (dmi_pready),
|
||||
// .dmi_pslverr (dmi_pslverr)
|
||||
// );
|
||||
|
||||
assign core_dr_rdata = 42'h555555550;
|
||||
|
||||
endmodule
|
|
@ -126,9 +126,6 @@ always @ (posedge tck or negedge trst_n) begin
|
|||
end
|
||||
end
|
||||
|
||||
localparam W_DR_SHIFT = W_ADDR + 32 + 2;
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Data registers
|
||||
|
||||
|
@ -190,9 +187,8 @@ assign core_dr_ren = (ir == IR_DMI || ir == IR_DTMCS) && tap_state == S_CAPTURE_
|
|||
assign core_dr_wdata = dr_shift;
|
||||
|
||||
hazard3_jtag_dtm_core #(
|
||||
.DTMCS_IDLE_HINT(DTMCS_IDLE_HINT),
|
||||
.W_ADDR(W_ADDR),
|
||||
.W_DR_SHIFT(W_DR_SHIFT)
|
||||
.DTMCS_IDLE_HINT (DTMCS_IDLE_HINT),
|
||||
.W_ADDR (W_ADDR)
|
||||
) dtm_core (
|
||||
.tck (tck),
|
||||
.trst_n (trst_n),
|
||||
|
|
Loading…
Reference in New Issue