Hazard3/hdl/debug/dtm/hazard3_uart_dtm.v

346 lines
10 KiB
Verilog

/**********************************************************************
* 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. *
* *
*********************************************************************/
// UART Debug Transport Module: connect an external two-wire 1 Mbaud UART
// interface to an APB Debug Module port.
//
// This is not suitable for production systems (it's a UART...) but is a
// simple way to get your FPGA board up and running.
module hazard3_uart_dtm #(
// Expected to run at 1 Mbaud from some fixed reference frequency.
parameter BAUD_CLKDIV = 12,
parameter DTM_ID_REG = 32'hdeadbeef,
parameter W_BAUDCTR = $clog2(BAUD_CLKDIV) // do not modify
) (
input wire clk,
input wire rst_n,
// External UART interface
input wire rx,
output wire tx,
// APB port to Debug Module
output wire psel,
output wire penable,
output wire pwrite,
output wire [7:0] paddr,
output wire [31:0] pwdata,
input wire [31:0] prdata,
input wire pready,
input wire pslverr
);
// ----------------------------------------------------------------------------
// Serial interface
wire [7:0] tx_wdata;
wire tx_wvld;
wire tx_wrdy;
wire [7:0] tx_rdata;
wire tx_rvld;
wire tx_rrdy = 1'b1;
wire [7:0] rx_wdata;
wire rx_wvld;
wire rx_wrdy;
wire [7:0] rx_rdata;
wire rx_rvld;
wire rx_rrdy;
hazard3_uart_dtm_fifo #(
.WIDTH(8),
.LOG_DEPTH(2)
) tx_fifo (
.clk (clk),
.rst_n (rst_n),
.wdata (tx_wdata),
.wvld (tx_wvld),
.wrdy (tx_wrdy),
.rdata (tx_rdata),
.rvld (tx_rvld),
.rrdy (tx_rrdy)
);
hazard3_uart_dtm_fifo #(
.WIDTH(8),
.LOG_DEPTH(2)
) rx_fifo (
.clk (clk),
.rst_n (rst_n),
.wdata (rx_wdata),
.wvld (rx_wvld),
.wrdy (rx_wrdy),
.rdata (rx_rdata),
.rvld (rx_rvld),
.rrdy (rx_rrdy)
);
reg [W_BAUDCTR-1:0] tx_baudctr;
reg [9:0] tx_shiftreg;
reg [3:0] tx_shiftctr;
assign tx_rrdy = ~|tx_shiftctr;
assign tx = tx_shiftreg[0];
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx_baudctr <= {W_BAUDCTR{1'b0}};
tx_shiftreg <= 10'h3ff;
tx_shiftctr <= 4'd0;
end else if (tx_rvld && tx_rrdy) begin
tx_baudctr <= BAUD_CLKDIV - 1;
tx_shiftreg <= {1'b1, tx_rdata, 1'b0};
tx_shiftctr <= 4'd10;
end else if (|tx_baudctr) begin
tx_baudctr <= tx_baudctr - 1'b1;
end else if (|tx_shiftctr) begin
tx_baudctr <= BAUD_CLKDIV - 1;
tx_shiftreg <= {1'b1, tx_shiftreg[9:1]};
tx_shiftctr <= tx_shiftctr - 1'b1;
end
end
wire rx_sync;
hazard3_sync_1bit #(
.N_STAGES (2)
) sync_req (
.clk (clk),
.rst_n (rst_n),
.i (rx),
.o (rx_sync)
);
reg [W_BAUDCTR-1:0] rx_baudctr;
reg [7:0] rx_shiftreg;
reg [3:0] rx_shiftctr;
// Only push if the frame ends with a valid stop bit:
assign rx_wvld = ~|rx_baudctr && rx_shiftctr == 4'd1 && rx_sync;
assign rx_wdata = rx_shiftreg;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_baudctr <= {W_BAUDCTR{1'b0}};
rx_shiftreg <= 8'h00;
rx_shiftctr <= 4'd0;
end else if (~|rx_shiftctr && ~|rx_baudctr && !rx_sync) begin
rx_shiftctr <= 4'd10;
// Start with half-period to get sampling alignment
rx_baudctr <= (BAUD_CLKDIV - 1) / 2;
end else if (|rx_baudctr) begin
rx_baudctr <= rx_baudctr - 1'b1;
end else if (|rx_shiftctr) begin
rx_baudctr <= BAUD_CLKDIV - 1;
rx_shiftctr <= rx_shiftctr - 1'b1;
if (rx_shiftctr != 4'd1 && rx_shiftctr != 4'd10)
rx_shiftreg <= {rx_sync, rx_shiftreg[7:1]};
end
end
// ----------------------------------------------------------------------------
// Command state machine
localparam W_STATE = 5;
localparam [W_STATE-1:0] S_DORMANT0 = 5'd0;
localparam [W_STATE-1:0] S_DORMANT1 = 5'd1;
localparam [W_STATE-1:0] S_DORMANT2 = 5'd2;
localparam [W_STATE-1:0] S_DORMANT3 = 5'd3;
localparam [W_STATE-1:0] S_CMD = 5'd4;
localparam [W_STATE-1:0] S_ID0 = 5'd5;
localparam [W_STATE-1:0] S_ID1 = 5'd6;
localparam [W_STATE-1:0] S_ID2 = 5'd7;
localparam [W_STATE-1:0] S_ID3 = 5'd8;
localparam [W_STATE-1:0] S_WADDR = 5'd9;
localparam [W_STATE-1:0] S_WDATA0 = 5'd10;
localparam [W_STATE-1:0] S_WDATA1 = 5'd11;
localparam [W_STATE-1:0] S_WDATA2 = 5'd12;
localparam [W_STATE-1:0] S_WDATA3 = 5'd13;
localparam [W_STATE-1:0] S_WSETUP = 5'd14;
localparam [W_STATE-1:0] S_WACCESS = 5'd15;
localparam [W_STATE-1:0] S_RADDR = 5'd16;
localparam [W_STATE-1:0] S_RSETUP = 5'd17;
localparam [W_STATE-1:0] S_RACCESS = 5'd18;
localparam [W_STATE-1:0] S_RDATA0 = 5'd19;
localparam [W_STATE-1:0] S_RDATA1 = 5'd20;
localparam [W_STATE-1:0] S_RDATA2 = 5'd21;
localparam [W_STATE-1:0] S_RDATA3 = 5'd22;
localparam CMD_NOP = 8'h00;
localparam CMD_ID = 8'h01;
localparam CMD_READ = 8'h02;
localparam CMD_WRITE = 8'h03;
localparam CMD_RETURN_TO_DORMANT = 8'ha5;
reg [W_STATE-1:0] state;
reg [7:0] dm_addr;
reg [31:0] dm_data;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= S_DORMANT0;
dm_addr <= 8'h0;
dm_data <= 32'h0;
end else case (state)
S_DORMANT0: if (rx_rvld) state <= rx_rdata == "S" ? S_DORMANT1 : S_DORMANT0;
S_DORMANT1: if (rx_rvld) state <= rx_rdata == "U" ? S_DORMANT2 : S_DORMANT0;
S_DORMANT2: if (rx_rvld) state <= rx_rdata == "P" ? S_DORMANT3 : S_DORMANT0;
S_DORMANT3: if (rx_rvld) state <= rx_rdata == "?" ? S_CMD : S_DORMANT0;
S_CMD: if (rx_rvld) begin
if (rx_rdata == CMD_READ)
state <= S_RADDR;
else if (rx_rdata == CMD_WRITE)
state <= S_WADDR;
else if (rx_rdata == CMD_ID)
state <= S_ID0;
else if (rx_rdata == CMD_RETURN_TO_DORMANT)
state <= S_DORMANT0;
// NOP or invalid leave DTM in command state.
end
S_ID0: if (tx_wrdy) state <= S_ID1;
S_ID1: if (tx_wrdy) state <= S_ID2;
S_ID2: if (tx_wrdy) state <= S_ID3;
S_ID3: if (tx_wrdy) state <= S_CMD;
S_WADDR: if (rx_rvld) begin
state <= S_WDATA0;
dm_addr <= rx_rdata;
end
S_WDATA0: if (rx_rvld) begin
state <= S_WDATA1;
dm_data <= {rx_rdata, dm_data[31:8]};
end
S_WDATA1: if (rx_rvld) begin
state <= S_WDATA2;
dm_data <= {rx_rdata, dm_data[31:8]};
end
S_WDATA2: if (rx_rvld) begin
state <= S_WDATA3;
dm_data <= {rx_rdata, dm_data[31:8]};
end
S_WDATA3: if (rx_rvld) begin
state <= S_WSETUP;
dm_data <= {rx_rdata, dm_data[31:8]};
end
S_WSETUP: state <= S_WACCESS;
S_WACCESS: if (pready) state <= S_CMD;
S_RADDR: if (rx_rvld) begin
state <= S_RSETUP;
dm_addr <= rx_rdata;
end
S_RSETUP: state <= S_RACCESS;
S_RACCESS: if (pready) begin
dm_data <= prdata;
state <= S_RDATA0;
end
S_RDATA0: if (tx_wrdy) begin
dm_data <= {rx_rdata, dm_data[31:8]};
state <= S_RDATA1;
end
S_RDATA1: if (tx_wrdy) begin
dm_data <= {rx_rdata, dm_data[31:8]};
state <= S_RDATA2;
end
S_RDATA2: if (tx_wrdy) begin
dm_data <= {rx_rdata, dm_data[31:8]};
state <= S_RDATA3;
end
S_RDATA3: if (tx_wrdy) begin
dm_data <= {rx_rdata, dm_data[31:8]};
state <= S_CMD;
end
endcase
end
// ----------------------------------------------------------------------------
// Bus & FIFO hookup
wire state_is_dormant =
state == S_DORMANT0 ||
state == S_DORMANT1 ||
state == S_DORMANT2 ||
state == S_DORMANT3;
wire state_is_id =
state == S_ID0 ||
state == S_ID1 ||
state == S_ID2 ||
state == S_ID3;
wire state_is_wdata =
state == S_WDATA0 ||
state == S_WDATA1 ||
state == S_WDATA2 ||
state == S_WDATA3;
wire state_is_rdata =
state == S_RDATA0 ||
state == S_RDATA1 ||
state == S_RDATA2 ||
state == S_RDATA3;
// Note we don't consume the read padding bytes during the read data phase --
// these are actually interpreted as NOPs preceding the next command.
// (They are still important for bus pacing though.)
assign rx_rrdy =
state_is_dormant ||
state == S_CMD ||
state == S_WADDR ||
state == S_RADDR ||
state_is_wdata;
assign tx_wvld =
(state_is_wdata && rx_rvld) ||
state_is_rdata ||
state_is_id;
assign tx_wdata =
state_is_wdata ? rx_rdata :
state == S_ID0 ? DTM_ID_REG[ 7: 0] :
state == S_ID1 ? DTM_ID_REG[15: 8] :
state == S_ID2 ? DTM_ID_REG[23:16] :
state == S_ID3 ? DTM_ID_REG[31:24] : dm_data[7:0];
assign psel =
state == S_WSETUP ||
state == S_WACCESS ||
state == S_RSETUP ||
state == S_RACCESS;
assign penable =
state == S_WACCESS ||
state == S_RACCESS;
assign pwrite =
state == S_WSETUP ||
state == S_WACCESS;
assign paddr = dm_addr;
assign pwdata = dm_data;
endmodule