Extract DTM bus/control logic from the JTAG-related parts

This commit is contained in:
Luke Wren 2021-07-22 19:25:50 +01:00
parent b0d11c0ab7
commit 41477ce479
4 changed files with 272 additions and 153 deletions

View File

@ -26,15 +26,15 @@ module example_soc (
input wire rst_n,
// JTAG port to RISC-V JTAG-DTM
input wire tck,
input wire trst_n,
input wire tms,
input wire tdi,
output wire tdo,
input wire tck,
input wire trst_n,
input wire tms,
input wire tdi,
output wire tdo,
// IO
output wire uart_tx,
input wire uart_rx
// IO
output wire uart_tx,
input wire uart_rx
);
localparam W_ADDR = 32;
@ -57,7 +57,7 @@ wire dmi_pready;
wire dmi_pslverr;
// TCK-domain DTM logic can force a hard reset of the
// TCK-domain DTM logic can force a hard reset of the
wire dmihardreset_req;
wire assert_dmi_reset = !rst_n || dmihardreset_req;
wire rst_n_dmi;

View File

@ -1,4 +1,5 @@
file hazard3_jtag_dtm.v
file hazard3_jtag_dtm_core.v
file ../cdc/hazard3_apb_async_bridge.v
file ../cdc/hazard3_reset_sync.v

View File

@ -24,31 +24,32 @@
module hazard3_jtag_dtm #(
parameter IDCODE = 32'h0000_0001,
parameter DTMCS_IDLE_HINT = 3'd4
parameter DTMCS_IDLE_HINT = 3'd4,
parameter W_ADDR = 8
) (
// Standard JTAG signals -- the JTAG hardware is clocked directly by TCK.
input wire tck,
input wire trst_n,
input wire tms,
input wire tdi,
output reg tdo,
input wire tck,
input wire trst_n,
input wire tms,
input wire tdi,
output reg tdo,
// This is synchronous to TCK and asserted for one TCK cycle only
output reg dmihardreset_req,
output wire dmihardreset_req,
// Bus clock + reset for Debug Module Interface
input wire clk_dmi,
input wire rst_n_dmi,
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 [7:0] dmi_paddr,
output wire [31:0] dmi_pwdata,
input wire [31:0] dmi_prdata,
input wire dmi_pready,
input wire dmi_pslverr
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
);
// ----------------------------------------------------------------------------
@ -125,15 +126,24 @@ always @ (posedge tck or negedge trst_n) begin
end
end
localparam W_DR_SHIFT = W_ADDR + 32 + 2;
// ----------------------------------------------------------------------------
// Data registers
// Shift register is sized to largest DR, which is DMI:
// {addr[7:0], data[31:0], op[1:0]}
localparam W_DR_SHIFT = 42;
localparam W_DR_SHIFT = W_ADDR + 32 + 2;
reg [W_DR_SHIFT-1:0] dr_shift;
reg [1:0] dmi_cmderr;
// Signals to/from the DTM core, which implements the DTMCS and DMI registers
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;
always @ (posedge tck or negedge trst_n) begin
if (!trst_n) begin
@ -144,26 +154,14 @@ always @ (posedge tck or negedge trst_n) begin
dr_shift <= {tdi, dr_shift} >> 1;
// Shorten DR shift chain according to IR
if (ir == IR_DMI)
dr_shift[41] <= tdi;
dr_shift[W_DR_SHIFT - 1] <= tdi;
else if (ir == IR_IDCODE || ir == IR_DTMCS)
dr_shift[31] <= tdi;
else // BYPASS
dr_shift[0] <= tdi;
end else if (tap_state == S_CAPTURE_DR) begin
if (ir == IR_DMI)
dr_shift <= {
8'h0,
dtm_prdata,
dmi_busy && dmi_cmderr == 2'd0 ? 2'd3 : dmi_cmderr
};
else if (ir == IR_DTMCS)
dr_shift <= {
27'h0,
DTMCS_IDLE_HINT,
dmi_cmderr,
6'd8, // abits
4'd1 // version
};
if (ir == IR_DMI || ir == IR_DTMCS)
dr_shift <= core_dr_rdata;
else if (ir == IR_IDCODE)
dr_shift <= {10'h0, IDCODE};
else // BYPASS
@ -171,116 +169,7 @@ always @ (posedge tck or negedge trst_n) begin
end
end
always @ (posedge tck or negedge trst_n) begin
if (!trst_n) begin
dmihardreset_req <= 1'b0;
end else begin
dmihardreset_req <= tap_state == S_UPDATE_DR && ir == IR_DTMCS && dr_shift[17];
end
end
// ----------------------------------------------------------------------------
// DMI bus adapter
reg dmi_busy;
// DTM-domain bus, connected to a matching DM-domain bus via an APB crossing:
wire dtm_psel;
wire dtm_penable;
wire dtm_pwrite;
wire [7:0] dtm_paddr;
wire [31:0] dtm_pwdata;
wire [31:0] dtm_prdata;
wire dtm_pready;
wire dtm_pslverr;
// We are relying on some particular features of our APB clock crossing here
// to save some registers:
//
// - The transfer is launched immediately when psel is seen, no need to
// actually assert an access phase (as the standard allows the CDC to
// assume that access immediately follows setup) and no need to maintain
// pwrite/paddr/pwdata valid after the setup phase
//
// - prdata/pslverr remain valid after the transfer completes, until the next
// transfer completes
//
// These allow us to connect the upstream side of the CDC directly to our DR
// shifter without any sample/hold registers in between.
// psel is only pulsed for one cycle, penable is not asserted.
assign dtm_psel = tap_state == S_UPDATE_DR && ir == IR_DMI &&
(dr_shift[1:0] == 2'd1 || dr_shift[1:0] == 2'd2) &&
!(dmi_busy || dmi_cmderr != 2'd0) && dtm_pready;
assign dtm_penable = 1'b0;
// paddr/pwdata/pwrite are valid momentarily when psel is asserted.
assign dtm_paddr = dr_shift[34 +: 8];
assign dtm_pwrite = dr_shift[1];
assign dtm_pwdata = dr_shift[2 +: 32];
always @ (posedge tck or negedge trst_n) begin
if (!trst_n) begin
dmi_busy <= 1'b0;
dmi_cmderr <= 2'd0;
end else if (tap_state == S_CAPTURE_IR && ir == IR_DMI) begin
// Reading while busy sets the busy sticky error. Note the capture
// into shift register should also reflect this update on-the-fly
if (dmi_busy && dmi_cmderr == 2'd0)
dmi_cmderr <= 2'h3;
end else if (tap_state == S_UPDATE_DR && ir == IR_DTMCS) begin
// Writing dtmcs.dmireset = 1 clears a sticky error
if (dr_shift[16])
dmi_cmderr <= 2'd0;
end else if (tap_state == S_UPDATE_DR && ir == IR_DMI) begin
if (dtm_psel) begin
dmi_busy <= 1'b1;
end else if (dr_shift[1:0] != 2'd0) begin
// DMI ignored operation, so set sticky busy
if (dmi_cmderr == 2'd0)
dmi_cmderr <= 2'd3;
end
end else if (dmi_busy && dtm_pready) begin
dmi_busy <= 1'b0;
if (dmi_cmderr == 2'd0 && dtm_pslverr)
dmi_cmderr <= 2'd2;
end
end
// DTM logic is in TCK domain, actual DMI + DM is in processor domain
hazard3_apb_async_bridge #(
.W_ADDR (8),
.W_DATA (32),
.N_SYNC_STAGES (2)
) inst_hazard3_apb_async_bridge (
.clk_src (tck),
.rst_n_src (trst_n),
.clk_dst (clk_dmi),
.rst_n_dst (rst_n_dmi),
.src_psel (dtm_psel),
.src_penable (dtm_penable),
.src_pwrite (dtm_pwrite),
.src_paddr (dtm_paddr),
.src_pwdata (dtm_pwdata),
.src_prdata (dtm_prdata),
.src_pready (dtm_pready),
.src_pslverr (dtm_pslverr),
.dst_psel (dmi_psel),
.dst_penable (dmi_penable),
.dst_pwrite (dmi_pwrite),
.dst_paddr (dmi_paddr),
.dst_pwdata (dmi_pwdata),
.dst_prdata (dmi_prdata),
.dst_pready (dmi_pready),
.dst_pslverr (dmi_pslverr)
);
// ----------------------------------------------------------------------------
// TDO negedge register
// Must retime shift data onto negedge before presenting on TDO
always @ (negedge tck or negedge trst_n) begin
if (!trst_n) begin
@ -291,4 +180,41 @@ always @ (negedge tck or negedge trst_n) begin
end
end
// ----------------------------------------------------------------------------
// Core logic and bus interface
assign core_dr_sel_dmi_ndtmcs = ir == IR_DMI;
assign core_dr_wen = (ir == IR_DMI || ir == IR_DTMCS) && tap_state == S_UPDATE_DR;
assign core_dr_ren = (ir == IR_DMI || ir == IR_DTMCS) && tap_state == S_CAPTURE_DR;
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)
) dtm_core (
.tck (tck),
.trst_n (trst_n),
.clk_dmi (clk_dmi),
.rst_n_dmi (rst_n_dmi),
.dmihardreset_req (dmihardreset_req),
.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),
.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)
);
endmodule

View File

@ -0,0 +1,192 @@
/**********************************************************************
* 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. *
* *
*********************************************************************/
// DTMCS + DMI control logic, bus interface and bus clock domain crossing for
// a standard RISC-V APB JTAG-DTM. Essentially everything apart from the
// actual TAP controller, IR and shift registers. Instantiated by
// hazard3_jtag_dtm.v.
//
// This core logic can be reused and connected to some other serial transport
// or, for example, the ECP5 JTAGG primitive (see hazard5_ecp5_jtag_dtm.v)
module hazard3_jtag_dtm_core #(
parameter DTMCS_IDLE_HINT = 3'd4,
parameter W_ADDR = 8,
parameter W_DR_SHIFT = W_ADDR + 32 + 2 // do not modify
) (
input wire tck,
input wire trst_n,
input wire clk_dmi,
input wire rst_n_dmi,
// DR capture/update (read/write) signals
input wire dr_wen,
input wire dr_ren,
input wire dr_sel_dmi_ndtmcs,
input wire [W_DR_SHIFT-1:0] dr_wdata,
output wire [W_DR_SHIFT-1:0] dr_rdata,
// This is synchronous to TCK and asserted for one TCK cycle only
output reg dmihardreset_req,
// 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
);
wire write_dmi = dr_wen && dr_sel_dmi_ndtmcs;
wire write_dtmcs = dr_wen && !dr_sel_dmi_ndtmcs;
wire read_dmi = dr_ren && dr_sel_dmi_ndtmcs;
wire read_dtmcs = dr_ren && !dr_sel_dmi_ndtmcs;
// ----------------------------------------------------------------------------
// DMI bus adapter
reg [1:0] dmi_cmderr;
reg dmi_busy;
// DTM-domain bus, connected to a matching DM-domain bus via an APB crossing:
wire dtm_psel;
wire dtm_penable;
wire dtm_pwrite;
wire [7:0] dtm_paddr;
wire [31:0] dtm_pwdata;
wire [31:0] dtm_prdata;
wire dtm_pready;
wire dtm_pslverr;
// We are relying on some particular features of our APB clock crossing here
// to save some registers:
//
// - The transfer is launched immediately when psel is seen, no need to
// actually assert an access phase (as the standard allows the CDC to
// assume that access immediately follows setup) and no need to maintain
// pwrite/paddr/pwdata valid after the setup phase
//
// - prdata/pslverr remain valid after the transfer completes, until the next
// transfer completes
//
// These allow us to connect the upstream side of the CDC directly to our DR
// shifter without any sample/hold registers in between.
// psel is only pulsed for one cycle, penable is not asserted.
assign dtm_psel = write_dmi &&
(dr_wdata[1:0] == 2'd1 || dr_wdata[1:0] == 2'd2) &&
!(dmi_busy || dmi_cmderr != 2'd0) && dtm_pready;
assign dtm_penable = 1'b0;
// paddr/pwdata/pwrite are valid momentarily when psel is asserted.
assign dtm_paddr = dr_wdata[34 +: 8];
assign dtm_pwrite = dr_wdata[1];
assign dtm_pwdata = dr_wdata[2 +: 32];
always @ (posedge tck or negedge trst_n) begin
if (!trst_n) begin
dmi_busy <= 1'b0;
dmi_cmderr <= 2'd0;
end else if (read_dmi) begin
// Reading while busy sets the busy sticky error. Note the capture
// into shift register should also reflect this update on-the-fly
if (dmi_busy && dmi_cmderr == 2'd0)
dmi_cmderr <= 2'h3;
end else if (write_dtmcs) begin
// Writing dtmcs.dmireset = 1 clears a sticky error
if (dr_wdata[16])
dmi_cmderr <= 2'd0;
end else if (write_dmi) begin
if (dtm_psel) begin
dmi_busy <= 1'b1;
end else if (dr_wdata[1:0] != 2'd0) begin
// DMI ignored operation, so set sticky busy
if (dmi_cmderr == 2'd0)
dmi_cmderr <= 2'd3;
end
end else if (dmi_busy && dtm_pready) begin
dmi_busy <= 1'b0;
if (dmi_cmderr == 2'd0 && dtm_pslverr)
dmi_cmderr <= 2'd2;
end
end
// DTM logic is in TCK domain, actual DMI + DM is in processor domain
hazard3_apb_async_bridge #(
.W_ADDR (W_ADDR),
.W_DATA (32),
.N_SYNC_STAGES (2)
) inst_hazard3_apb_async_bridge (
.clk_src (tck),
.rst_n_src (trst_n),
.clk_dst (clk_dmi),
.rst_n_dst (rst_n_dmi),
.src_psel (dtm_psel),
.src_penable (dtm_penable),
.src_pwrite (dtm_pwrite),
.src_paddr (dtm_paddr),
.src_pwdata (dtm_pwdata),
.src_prdata (dtm_prdata),
.src_pready (dtm_pready),
.src_pslverr (dtm_pslverr),
.dst_psel (dmi_psel),
.dst_penable (dmi_penable),
.dst_pwrite (dmi_pwrite),
.dst_paddr (dmi_paddr),
.dst_pwdata (dmi_pwdata),
.dst_prdata (dmi_prdata),
.dst_pready (dmi_pready),
.dst_pslverr (dmi_pslverr)
);
// ----------------------------------------------------------------------------
// DR read/write
wire [W_DR_SHIFT-1:0] dtmcs_rdata = {
{W_ADDR{1'b0}},
19'h0,
DTMCS_IDLE_HINT[2:0],
dmi_cmderr,
6'd8, // abits
4'd1 // version
};
wire [W_DR_SHIFT-1:0] dmi_rdata = {
{W_ADDR{1'b0}},
dtm_prdata,
dmi_busy && dmi_cmderr == 2'd0 ? 2'd3 : dmi_cmderr
};
assign dr_rdata = dr_sel_dmi_ndtmcs ? dmi_rdata : dtmcs_rdata;
always @ (posedge tck or negedge trst_n) begin
if (!trst_n) begin
dmihardreset_req <= 1'b0;
end else begin
dmihardreset_req <= write_dtmcs && dr_wdata[17];
end
end
endmodule