diff --git a/hdl/debug/dtm/hazard3_apb_async_bridge.v b/hdl/debug/cdc/hazard3_apb_async_bridge.v similarity index 89% rename from hdl/debug/dtm/hazard3_apb_async_bridge.v rename to hdl/debug/cdc/hazard3_apb_async_bridge.v index 6389bb6..5eeede8 100644 --- a/hdl/debug/dtm/hazard3_apb_async_bridge.v +++ b/hdl/debug/cdc/hazard3_apb_async_bridge.v @@ -28,7 +28,8 @@ module hazard3_apb_async_bridge #( parameter W_ADDR = 8, - parameter W_DATA = 32 + parameter W_DATA = 32, + parameter N_SYNC_STAGES = 2 ) ( // Resets assumed to be synchronised externally input wire clk_src, @@ -58,8 +59,6 @@ module hazard3_apb_async_bridge #( input wire dst_pslverr ); -localparam N_SYNC_STAGES = 3; - // ---------------------------------------------------------------------------- // Clock-crossing registers @@ -83,11 +82,18 @@ wire dst_req; `HAZARD3_REG_KEEP_ATTRIBUTE reg dst_ack; wire src_ack; -`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_ADDR + W_DATA + 1 -1:0] src_paddr_pwdata_pwrite; -`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_ADDR + W_DATA + 1 -1:0] dst_paddr_pwdata_pwrite; +// Note the launch registers are not resettable. We maintain setup/hold on +// launch-to-capture paths thanks to the req/ack handshake. A stray reset +// could violate this. +// +// The req/ack logic itself can be reset safely because the receiving domain +// is protected from metastability by a 2FF synchroniser. -`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_DATA + 1 -1:0] dst_prdata_pslverr; -`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_DATA + 1 -1:0] src_prdata_pslverr; +`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_ADDR + W_DATA + 1 -1:0] src_paddr_pwdata_pwrite; // launch +`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_ADDR + W_DATA + 1 -1:0] dst_paddr_pwdata_pwrite; // capture + +`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_DATA + 1 -1:0] dst_prdata_pslverr; // launch +`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_DATA + 1 -1:0] src_prdata_pslverr; // capture hazard3_sync_1bit #( .N_STAGES (N_SYNC_STAGES) @@ -139,12 +145,17 @@ always @ (posedge clk_src or negedge rst_n_src) begin if (src_psel) begin src_pready_r <= 1'b0; src_req <= 1'b1; - src_paddr_pwdata_pwrite <= {src_paddr, src_pwdata, src_pwrite}; src_waiting_for_downstream <= 1'b1; end end end +// Bus request launch register is not resettable +always @ (posedge clk_src) begin + if (src_psel && !src_waiting_for_downstream) + src_paddr_pwdata_pwrite <= {src_paddr, src_pwdata, src_pwrite}; +end + assign {src_prdata, src_pslverr} = src_prdata_pslverr; assign src_pready = src_pready_r; @@ -181,10 +192,15 @@ always @ (posedge clk_dst or negedge rst_n_dst) begin end else if (dst_bus_finish) begin dst_psel_r <= 1'b0; dst_penable_r <= 1'b0; - dst_prdata_pslverr <= {dst_prdata, dst_pslverr}; end end +// Bus response launch register is not resettable +always @ (posedge clk_dst) begin + if (dst_bus_finish) + dst_prdata_pslverr <= {dst_prdata, dst_pslverr}; +end + assign dst_psel = dst_psel_r; assign dst_penable = dst_penable_r; assign {dst_paddr, dst_pwdata, dst_pwrite} = dst_paddr_pwdata_pwrite; diff --git a/hdl/debug/cdc/hazard3_reset_sync.v b/hdl/debug/cdc/hazard3_reset_sync.v new file mode 100644 index 0000000..f023a33 --- /dev/null +++ b/hdl/debug/cdc/hazard3_reset_sync.v @@ -0,0 +1,47 @@ +/********************************************************************** + * 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 output is asserted asynchronously when the input is asserted, +// but deasserted synchronously when clocked with the input deasserted. +// Input and output are both active-low. +// +// This is a baseline implementation -- you should replace it with cells +// specific to your FPGA/process + +`ifndef HAZARD3_REG_KEEP_ATTRIBUTE +`define HAZARD3_REG_KEEP_ATTRIBUTE (* keep = 1'b1 *) +`endif + +module hazard3_reset_sync #( + parameter N_STAGES = 2 // Should be >= 2 +) ( + input wire clk, + input wire rst_n_in, + output wire rst_n_out +); + +`HAZARD3_REG_KEEP_ATTRIBUTE reg [N_STAGES-1:0] delay; + +always @ (posedge clk or negedge rst_n_in) + if (!rst_n_in) + delay <= {N_STAGES{1'b0}}; + else + delay <= {delay[N_STAGES-2:0], 1'b1}; + +assign rst_n_out = delay[N_STAGES-1]; + +endmodule diff --git a/hdl/debug/dtm/hazard3_sync_1bit.v b/hdl/debug/cdc/hazard3_sync_1bit.v similarity index 100% rename from hdl/debug/dtm/hazard3_sync_1bit.v rename to hdl/debug/cdc/hazard3_sync_1bit.v diff --git a/hdl/debug/dm/hazard3_dm.v b/hdl/debug/dm/hazard3_dm.v index 88a82c2..86b388f 100644 --- a/hdl/debug/dm/hazard3_dm.v +++ b/hdl/debug/dm/hazard3_dm.v @@ -69,7 +69,7 @@ module hazard3_dm #( input wire [N_HARTS*XLEN-1:0] hart_data0_rdata, output wire [N_HARTS*XLEN-1:0] hart_data0_wdata, output wire [N_HARTS-1:0] hart_data0_wen, - + // Hart instruction injection output wire [N_HARTS*XLEN-1:0] hart_instr_data, output wire [N_HARTS-1:0] hart_instr_data_vld, @@ -353,7 +353,7 @@ always @ (posedge clk or negedge rst_n) begin else if (acmd_postexec) acmd_state <= S_ISSUE_PROGBUF0; else - acmd_state <= S_IDLE; + acmd_state <= S_IDLE; end end end diff --git a/hdl/debug/dtm/hazard3_jtag_dtm.f b/hdl/debug/dtm/hazard3_jtag_dtm.f new file mode 100644 index 0000000..6615d98 --- /dev/null +++ b/hdl/debug/dtm/hazard3_jtag_dtm.f @@ -0,0 +1,5 @@ +file hazard3_jtag_dtm.v + +file ../cdc/hazard3_apb_async_bridge.v +file ../cdc/hazard3_reset_sync.v +file ../cdc/hazard3_sync_1bit.v diff --git a/hdl/debug/dtm/hazard3_jtag_dtm.v b/hdl/debug/dtm/hazard3_jtag_dtm.v new file mode 100644 index 0000000..52a0dd7 --- /dev/null +++ b/hdl/debug/dtm/hazard3_jtag_dtm.v @@ -0,0 +1,291 @@ +/********************************************************************** + * 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. * + * * + *********************************************************************/ + +// Implementation of standard RISC-V JTAG-DTM with an APB Debug Module +// Interface. The TAP itself is clocked directly by JTAG TCK; a clock +// crossing is instantiated internally between the TCK domain and the DMI bus +// clock domain. + +`default_nettype none + +module hazard3_jtag_dtm #( + parameter IDCODE = 32'h0000_0001, + parameter DTMCS_IDLE_HINT = 3'd4 +) ( + // 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, + + // This is synchronous to TCK and asserted for one TCK cycle only + output reg 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 [7:0] dmi_paddr, + output wire [31:0] dmi_pwdata, + input wire [31:0] dmi_prdata, + input wire dmi_pready, + input wire dmi_pslverr +); + +// ---------------------------------------------------------------------------- +// TAP state machine + +reg [3:0] tap_state; +localparam S_RESET = 4'd0; +localparam S_RUN_IDLE = 4'd1; +localparam S_SELECT_DR = 4'd2; +localparam S_CAPTURE_DR = 4'd3; +localparam S_SHIFT_DR = 4'd4; +localparam S_EXIT1_DR = 4'd5; +localparam S_PAUSE_DR = 4'd6; +localparam S_EXIT2_DR = 4'd7; +localparam S_UPDATE_DR = 4'd8; +localparam S_SELECT_IR = 4'd9; +localparam S_CAPTURE_IR = 4'd10; +localparam S_SHIFT_IR = 4'd11; +localparam S_EXIT1_IR = 4'd12; +localparam S_PAUSE_IR = 4'd13; +localparam S_EXIT2_IR = 4'd14; +localparam S_UPDATE_IR = 4'd15; + +always @ (posedge tck or negedge trst_n) begin + if (!trst_n) begin + tap_state <= S_RESET; + end else case(tap_state) + S_RESET : tap_state <= tms ? S_RESET : S_RUN_IDLE ; + S_RUN_IDLE : tap_state <= tms ? S_SELECT_DR : S_RUN_IDLE ; + + S_SELECT_DR : tap_state <= tms ? S_SELECT_IR : S_CAPTURE_DR; + S_CAPTURE_DR : tap_state <= tms ? S_EXIT1_DR : S_SHIFT_DR ; + S_SHIFT_DR : tap_state <= tms ? S_EXIT1_DR : S_SHIFT_DR ; + S_EXIT1_DR : tap_state <= tms ? S_UPDATE_DR : S_PAUSE_DR ; + S_PAUSE_DR : tap_state <= tms ? S_EXIT2_DR : S_PAUSE_DR ; + S_EXIT2_DR : tap_state <= tms ? S_UPDATE_DR : S_SHIFT_DR ; + S_UPDATE_DR : tap_state <= tms ? S_SELECT_DR : S_RUN_IDLE ; + + S_SELECT_IR : tap_state <= tms ? S_RESET : S_CAPTURE_IR; + S_CAPTURE_IR : tap_state <= tms ? S_EXIT1_IR : S_SHIFT_IR ; + S_SHIFT_IR : tap_state <= tms ? S_EXIT1_IR : S_SHIFT_IR ; + S_EXIT1_IR : tap_state <= tms ? S_UPDATE_IR : S_PAUSE_IR ; + S_PAUSE_IR : tap_state <= tms ? S_EXIT2_IR : S_PAUSE_IR ; + S_EXIT2_IR : tap_state <= tms ? S_UPDATE_IR : S_SHIFT_IR ; + S_UPDATE_IR : tap_state <= tms ? S_SELECT_DR : S_RUN_IDLE ; + endcase +end + +// ---------------------------------------------------------------------------- +// Instruction register + +localparam W_IR = 5; +// All other encodings behave as BYPASS: +localparam IR_IDCODE = 5'h01; +localparam IR_DTMCS = 5'h10; +localparam IR_DMI = 5'h11; + +reg [W_IR-1:0] ir_shift; +reg [W_IR-1:0] ir; + +always @ (posedge tck or negedge trst_n) begin + if (!trst_n) begin + ir_shift <= {W_IR{1'b0}}; + ir <= IR_IDCODE; + end else if (tap_state == S_RESET) begin + ir_shift <= {W_IR{1'b0}}; + ir <= IR_IDCODE; + end else if (tap_state == S_CAPTURE_IR) begin + ir_shift <= ir; + end else if (tap_state == S_SHIFT_IR) begin + ir_shift <= {ir_shift[W_IR-2:0], tdi}; + end else if (tap_state == S_UPDATE_IR) begin + ir <= ir_shift; + end +end + +// ---------------------------------------------------------------------------- +// 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; + +reg [W_DR_SHIFT-1:0] dr_shift; +reg [1:0] dmi_cmderr; + +always @ (posedge tck or negedge trst_n) begin + if (!trst_n) begin + dr_shift <= {W_DR_SHIFT{1'b0}}; + end else if (tap_state == S_RESET) begin + dr_shift <= {W_DR_SHIFT{1'b0}}; + end else if (tap_state == S_SHIFT_DR) begin + dr_shift <= {tdi, dr_shift} >> 1; + // Shorten DR shift chain according to IR + if (ir == IR_DMI) + dr_shift[41] <= 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 + }; + else if (ir == IR_IDCODE) + dr_shift <= {10'h0, IDCODE}; + else // BYPASS + dr_shift <= 42'h0; + 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) + dmi_busy <= 1'b1; + else if (dmi_cmderr == 2'd0) + dmi_cmderr <= 2'd3; + 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 + +always @ (negedge tck or negedge trst_n) begin + if (!trst_n) begin + tdo <= 1'b0; + end else begin + tdo <= tap_state == S_SHIFT_IR ? ir_shift[W_IR - 1] : + tap_state == S_SHIFT_DR ? dr_shift[W_DR_SHIFT - 1] : 1'b0; + end +end + +endmodule diff --git a/hdl/debug/dtm/hazard3_uart_dtm.f b/hdl/debug/dtm/hazard3_uart_dtm.f index da3f951..58a38da 100644 --- a/hdl/debug/dtm/hazard3_uart_dtm.f +++ b/hdl/debug/dtm/hazard3_uart_dtm.f @@ -1,6 +1,4 @@ file hazard3_uart_dtm.v file hazard3_uart_dtm_fifo.v -file hazard3_sync_1bit.v -# Optional clock crossing for DTM-to-DM bus: -file hazard3_apb_async_bridge.v +file ../cdc/hazard3_sync_1bit.v