Add Read ID command to UART DTM

This commit is contained in:
Luke Wren 2021-07-10 16:14:35 +01:00
parent 3312ea7022
commit 83244c6651
7 changed files with 2526 additions and 971 deletions

File diff suppressed because it is too large Load Diff

View File

@ -24,29 +24,57 @@ The DM implements abstract GPR access by injecting a dummy CSR access instructio
The debugger implements memory and CSR access using the Program Buffer, which uses the same instruction injection interface used by the DM to implement abstract GPR access. The `abstractauto` feature allows the DM to execute the program buffer automatically following every abstract GPR access, which can be used for e.g. autoincrementing read/write memory bursts. The debugger implements memory and CSR access using the Program Buffer, which uses the same instruction injection interface used by the DM to implement abstract GPR access. The `abstractauto` feature allows the DM to execute the program buffer automatically following every abstract GPR access, which can be used for e.g. autoincrementing read/write memory bursts.
=== Implementation-defined behaviour
This is not an exhaustive list (yet).
DM feature support:
* Abstract CSR and memory access are not implemented
* The Program Buffer is implemented, size 2 words, `impebreak` = 1.
* A single data register (`data0`) is implemented as a per-hart CSR accessible by the DM
* `abstractauto` is supported on the program buffer registers and the data register
* Multiple hart selection (`hasel` = 1) is not supported
Core behaviour:
* All control transfer instructions are illegal in debug mode (depend on value of PC)
* `auipc` is illegal in debug mode (depends on value of PC)
* The `dret` instruction is not supported (a special purpose DM-to-core signal is used to signal resume)
* Entering and exiting debug mode does not clear an atomic load reservation; the host may explicitly clear a reservation using a dummy `sc` instruction via the program buffer.
* The `dscratch` CSRs are not implemented
* `data0` is implemented as a scratch CSR mapped at `0x7b2` (the location of `dscratch0`), readable and writable by the debugger.
* `dcsr.stepie` is hardwired to 0 (no interrupts during single stepping)
* `dcsr.stopcount` and `dcsr.stoptime` are hardwired to 1 (no counter/timer increment in debug mode)
* `dcsr.mprven` is hardwired to 0
* `dcsr.prv` is hardwired to 3 (M-mode)
=== UART DTM === UART DTM
Hazard3 defines a minimal UART Debug Transport Module, which allows the Debug Module to be accessed via a standard 8n1 asynchronous serial port. The UART DTM is always accessed by the host using a two-wire serial interface (TXD RXD) running at 1 Mbaud. The interface between the DTM and DM is an AMBA 3 APB port with a 32-bit data bus and 8-bit address bus. Hazard3 defines a minimal UART Debug Transport Module, which allows the Debug Module to be accessed via a standard 8n1 asynchronous serial port. The UART DTM is always accessed by the host using a two-wire serial interface (TXD RXD) running at 1 Mbaud. The interface between the DTM and DM is an AMBA 3 APB port with a 32-bit data bus and 8-bit address bus.
This is not intended for production systems: This is a quick hack, and not suitable for production systems:
* Debug hardware should not expect a frequency reference for a UART to be present * Debug hardware should not expect a frequency reference for a UART to be present
* The UART DTM does not implement any flow control or error detection/correction * The UART DTM does not implement any flow control or error detection/correction
However, it suffices for bringup and playing around on FPGA boards. The host sends a 6-byte packet: The host may send the following commands:
* Command: [cols="20h,~,~", options="header"]
** `0x00` nop, ignored, next command can follow immediately (no address or data bytes, no response) |===
** `0x01` read | Command | To DTM | From DTM
** `0x02` write | `0x00` NOP | - | -
** `0xa5` return to idle (no address or data bytes, no response) | `0x01` Read ID | - | 4-byte ID, same format as JTAG-DTM ID (JEP106-compatible)
* One address byte | `0x02` Read DMI | 1 address byte | 4 data bytes
* 4 data bytes (write) or 4 zero-padding bytes (read) | `0x03` Write DMI | 1 address byte, 4 data bytes | data bytes echoed back
| `0xa5` Disconnect | - | -
|===
The 6-byte framing can be recovered at any time by writing 6 zero-bytes, which will be interpreted as between 1 and 6 nops depending on current DTM state. Initially after power-on the DTM is in the Dormant state, and will ignore any commands. The host sends the magic sequence `"SUP?"` (`0x53, 0x55, 0x50, 0x3f`) to wake the DTM, and then issues a Read ID command to check the link is up. The DTM can be returned to the Dormant state at any time using the `0xa5` Disconnect command.
The DTM always responds with four data bytes. For a read command this will be the data read from the given address. For a write command this will echo back the write data. So that the host can queue up batches of commands in its transmit buffer, without overrunning the DTM's transmit bandwidth, it's recommended to pad each command with NOPs so that it is strictly larger than the response. For example, a Read ID should be followed by four NOPs, and a Read DMI should be followed by 3 NOPs.
This interface assumes the actual data transfer takes very little time compared with the UART access (typically less than one baud period). Because the host-to-DTM bandwidth is always greater than the DTM-to-host bandwidth, the host can queue up batches of commands in its transmit buffer, and this should never overrun the DTM's response channel. So, the 1 Mbaud 8n1 UART link provides 67 kB/s of half-duplex data bandwidth between host and DM, which is enough to get your system off the ground. To recover command framing, write 6 NOP commands (the length of the longest commands). This will be interpreted as between 1 and 6 NOPs depending on the DTM's state.
This interface assumes the DMI data transfer takes very little time compared with the UART access (typically less than one baud period). When the host-to-DTM bandwidth is kept greater than the DTM-to-host bandwidth, thanks to appropriate NOP padding, the host can queue up batches of commands in its transmit buffer, and this should never overrun the DTM's response channel. So, the 1 Mbaud 8n1 UART link provides 67 kB/s of half-duplex data bandwidth between host and DM, which is enough to get your system off the ground.
Initially after power-on the DTM is in the idle state, and will ignore any commands. The host sends the magic sequence `'S', 'U', 'P', '?'` (`0x53, 0x55, 0x50, 0x3f`) to wake the DTM, and then attempts to access a read-only DM register such as `dmstatus` to make sure the link is up. The DTM can be returned to the idle at any time using the `0xa5` return-to-idle command.

View File

@ -24,6 +24,7 @@
module hazard3_uart_dtm #( module hazard3_uart_dtm #(
// Expected to run at 1 Mbaud from some fixed reference frequency. // Expected to run at 1 Mbaud from some fixed reference frequency.
parameter BAUD_CLKDIV = 12, parameter BAUD_CLKDIV = 12,
parameter DTM_ID_REG = 32'hdeadbeef,
parameter W_BAUDCTR = $clog2(BAUD_CLKDIV) // do not modify parameter W_BAUDCTR = $clog2(BAUD_CLKDIV) // do not modify
) ( ) (
input wire clk, input wire clk,
@ -157,61 +158,73 @@ end
localparam W_STATE = 5; localparam W_STATE = 5;
localparam [W_STATE-1:0] S_IDLE0 = 5'd0; localparam [W_STATE-1:0] S_DORMANT0 = 5'd0;
localparam [W_STATE-1:0] S_IDLE1 = 5'd1; localparam [W_STATE-1:0] S_DORMANT1 = 5'd1;
localparam [W_STATE-1:0] S_IDLE2 = 5'd2; localparam [W_STATE-1:0] S_DORMANT2 = 5'd2;
localparam [W_STATE-1:0] S_IDLE3 = 5'd3; localparam [W_STATE-1:0] S_DORMANT3 = 5'd3;
localparam [W_STATE-1:0] S_CMD = 5'd4; localparam [W_STATE-1:0] S_CMD = 5'd4;
localparam [W_STATE-1:0] S_WADDR = 5'd5; localparam [W_STATE-1:0] S_ID0 = 5'd5;
localparam [W_STATE-1:0] S_WDATA0 = 5'd6; localparam [W_STATE-1:0] S_ID1 = 5'd6;
localparam [W_STATE-1:0] S_WDATA1 = 5'd7; localparam [W_STATE-1:0] S_ID2 = 5'd7;
localparam [W_STATE-1:0] S_WDATA2 = 5'd8; localparam [W_STATE-1:0] S_ID3 = 5'd8;
localparam [W_STATE-1:0] S_WDATA3 = 5'd9;
localparam [W_STATE-1:0] S_WSETUP = 5'd10;
localparam [W_STATE-1:0] S_WACCESS = 5'd11;
localparam [W_STATE-1:0] S_RADDR = 5'd12; localparam [W_STATE-1:0] S_WADDR = 5'd9;
localparam [W_STATE-1:0] S_RSETUP = 5'd13; localparam [W_STATE-1:0] S_WDATA0 = 5'd10;
localparam [W_STATE-1:0] S_RACCESS = 5'd14; localparam [W_STATE-1:0] S_WDATA1 = 5'd11;
localparam [W_STATE-1:0] S_RDATA0 = 5'd15; localparam [W_STATE-1:0] S_WDATA2 = 5'd12;
localparam [W_STATE-1:0] S_RDATA1 = 5'd16; localparam [W_STATE-1:0] S_WDATA3 = 5'd13;
localparam [W_STATE-1:0] S_RDATA2 = 5'd17; localparam [W_STATE-1:0] S_WSETUP = 5'd14;
localparam [W_STATE-1:0] S_RDATA3 = 5'd18; 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_NOP = 8'h00;
localparam CMD_READ = 8'h01; localparam CMD_ID = 8'h01;
localparam CMD_WRITE = 8'h02; localparam CMD_READ = 8'h02;
localparam CMD_RETURN_TO_IDLE = 8'ha5; localparam CMD_WRITE = 8'h03;
localparam CMD_RETURN_TO_DORMANT = 8'ha5;
reg [W_STATE-1:0] state; reg [W_STATE-1:0] state;
reg [7:0] dm_addr; reg [7:0] dm_addr;
reg [31:0] dm_data; reg [31:0] dm_data;
always @ (posedge clk or negedge rst_n) begin always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin if (!rst_n) begin
state <= S_IDLE0; state <= S_DORMANT0;
dm_addr <= 8'h0; dm_addr <= 8'h0;
dm_data <= 32'h0; dm_data <= 32'h0;
end else case (state) end else case (state)
S_IDLE0: if (rx_rvld) state <= rx_rdata == "S" ? S_IDLE1 : S_IDLE0; S_DORMANT0: if (rx_rvld) state <= rx_rdata == "S" ? S_DORMANT1 : S_DORMANT0;
S_IDLE1: if (rx_rvld) state <= rx_rdata == "U" ? S_IDLE2 : S_IDLE0; S_DORMANT1: if (rx_rvld) state <= rx_rdata == "U" ? S_DORMANT2 : S_DORMANT0;
S_IDLE2: if (rx_rvld) state <= rx_rdata == "P" ? S_IDLE3 : S_IDLE0; S_DORMANT2: if (rx_rvld) state <= rx_rdata == "P" ? S_DORMANT3 : S_DORMANT0;
S_IDLE3: if (rx_rvld) state <= rx_rdata == "?" ? S_CMD : S_IDLE0; S_DORMANT3: if (rx_rvld) state <= rx_rdata == "?" ? S_CMD : S_DORMANT0;
S_CMD: if (rx_rvld) begin S_CMD: if (rx_rvld) begin
if (rx_rdata == CMD_READ) if (rx_rdata == CMD_READ)
state <= S_RADDR; state <= S_RADDR;
else if (rx_rdata == CMD_WRITE) else if (rx_rdata == CMD_WRITE)
state <= S_WADDR; state <= S_WADDR;
else if (rx_rdata == CMD_RETURN_TO_IDLE) else if (rx_rdata == CMD_ID)
state <= S_IDLE0; state <= S_ID0;
else if (rx_rdata == CMD_RETURN_TO_DORMANT)
state <= S_DORMANT0;
// NOP or invalid leave DTM in command state. // NOP or invalid leave DTM in command state.
end end
S_WADDR: if (rx_rvld) begin 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; state <= S_WDATA0;
dm_addr <= rx_rdata; dm_addr <= rx_rdata;
end end
@ -266,11 +279,17 @@ end
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Bus & FIFO hookup // Bus & FIFO hookup
wire state_is_idle = wire state_is_dormant =
state == S_IDLE0 || state == S_DORMANT0 ||
state == S_IDLE1 || state == S_DORMANT1 ||
state == S_IDLE2 || state == S_DORMANT2 ||
state == S_IDLE3; state == S_DORMANT3;
wire state_is_id =
state == S_ID0 ||
state == S_ID1 ||
state == S_ID2 ||
state == S_ID3;
wire state_is_wdata = wire state_is_wdata =
state == S_WDATA0 || state == S_WDATA0 ||
@ -288,14 +307,23 @@ wire state_is_rdata =
// these are actually interpreted as NOPs preceding the next command. // these are actually interpreted as NOPs preceding the next command.
// (They are still important for bus pacing though.) // (They are still important for bus pacing though.)
assign rx_rrdy = assign rx_rrdy =
state_is_idle || state_is_dormant ||
state == S_CMD || state == S_CMD ||
state == S_WADDR || state == S_WADDR ||
state == S_RADDR || state == S_RADDR ||
state_is_wdata; state_is_wdata;
assign tx_wdata = state_is_wdata ? rx_rdata : dm_data[7:0]; assign tx_wvld =
assign tx_wvld = (state_is_wdata && state_is_wdata) || state_is_rdata; (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 = assign psel =
state == S_WSETUP || state == S_WSETUP ||