181 lines
5.6 KiB
Plaintext
181 lines
5.6 KiB
Plaintext
package Debugger;
|
|
|
|
import GetPut::*;
|
|
import ClientServer::*;
|
|
import FIFOF::*;
|
|
import SpecialFIFOs::*;
|
|
|
|
import VRAM::*;
|
|
|
|
export ReadRange(..);
|
|
export DebugRequest(..);
|
|
export DebugResponse(..);
|
|
export Debugger(..), mkDebugger;
|
|
|
|
// ReadRange is a request to read a range of bytes.
|
|
typedef struct {
|
|
VRAMAddr start;
|
|
UInt#(8) count;
|
|
} ReadRange deriving (Eq, FShow);
|
|
|
|
// DebugRequest is a request sent to the debugger.
|
|
typedef union tagged {
|
|
// Ping is a probe to check the connection to the debugger. The
|
|
// debugger responds with 0xEA (Ω in code page 437) to identify
|
|
// itself as a GARY debugger. The eschatological symbol for the end
|
|
// of everything seems appropriate for the level of desperation
|
|
// where you reach for the debug interface.
|
|
//
|
|
// Did you know Ω featured on the mission patch of STS-135, the
|
|
// final flight of the US Space Shuttle program?
|
|
//
|
|
// "Having fired the imagination of a generation,
|
|
// a ship like no other, its place in history secured,
|
|
// the space shuttle pulls into port for the last time.
|
|
// Its voyage, at an end."
|
|
void Ping;
|
|
// MemByteOp requests a read or write of a single byte of
|
|
// memory. The associated VRAMRequest gets passed unmodified to the
|
|
// memory port. For reads, produces a single VRAMResponse with the
|
|
// data byte. For writes, no response.
|
|
VRAMRequest MemByteOp;
|
|
// ReadRange requests a read of a range of memory. It's equivalent
|
|
// to N read MemByteOps, but the memory operations are pipelined,
|
|
// enabling 3x faster readout.
|
|
ReadRange ReadRange;
|
|
} DebugRequest deriving (Eq, FShow);
|
|
|
|
// DebugResponse is the debugger's response to a command. Depending on
|
|
// the command, one DebugRequest may result in 0, 1 or many
|
|
// DebugResponses. Requests are fully processed and responded to
|
|
// before following requests are processed.
|
|
typedef VRAMResponse DebugResponse;
|
|
|
|
// DebugCmd is the value of the Cmd byte in raw encoded RawDebugReq.
|
|
typedef enum {
|
|
Ping = 'h7F, // To force the enum to be Bit#(7)
|
|
ReadByte = 1,
|
|
WriteByte = 2,
|
|
ReadRange = 3
|
|
} DebugCmd deriving (Bits, Eq, FShow);
|
|
|
|
// RawDebugReq is the bit encoding of a DebugRequest. It is 32 bits
|
|
// for all commands, in a format that's reasonably easy to assemble
|
|
// from the client side.
|
|
typedef struct {
|
|
DebugCmd cmd;
|
|
VRAMAddr addr;
|
|
VRAMData data;
|
|
} RawDebugReq deriving (Bits, Eq, FShow);
|
|
|
|
instance Bits#(DebugRequest, 32);
|
|
function Bit#(32) pack(DebugRequest r);
|
|
case (r) matches
|
|
tagged Ping:
|
|
return pack(RawDebugReq{
|
|
cmd: Ping,
|
|
addr: 0,
|
|
data: 0
|
|
});
|
|
tagged MemByteOp .req:
|
|
if (req.data matches tagged Valid .data)
|
|
return pack(RawDebugReq{
|
|
cmd: WriteByte,
|
|
addr: req.addr,
|
|
data: data
|
|
});
|
|
else
|
|
return pack(RawDebugReq{
|
|
cmd: ReadByte,
|
|
addr: req.addr,
|
|
data: 0
|
|
});
|
|
tagged ReadRange .req:
|
|
return pack(RawDebugReq{
|
|
cmd: ReadRange,
|
|
addr: req.start,
|
|
data: unpack(pack(req.count))
|
|
});
|
|
endcase
|
|
endfunction
|
|
|
|
function DebugRequest unpack(Bit#(32) b);
|
|
RawDebugReq raw = unpack(b);
|
|
case (raw.cmd)
|
|
Ping: return tagged Ping;
|
|
ReadByte: return tagged MemByteOp VRAMRequest{addr: raw.addr, data: tagged Invalid};
|
|
WriteByte: return tagged MemByteOp VRAMRequest{addr: raw.addr, data: tagged Valid raw.data};
|
|
ReadRange: return tagged ReadRange ReadRange{start: raw.addr, count: unpack(pack(raw.data))};
|
|
endcase
|
|
endfunction
|
|
endinstance
|
|
|
|
// A Debugger provides debug access to GARY.
|
|
interface Debugger;
|
|
interface Server#(DebugRequest, DebugResponse) server;
|
|
interface Client#(VRAMRequest, VRAMResponse) vram;
|
|
endinterface
|
|
|
|
// mkDebugger constructs a GARY debugger.
|
|
(* descending_urgency="mem_response,issue_range,start_req" *)
|
|
module mkDebugger(Debugger);
|
|
FIFOF#(DebugRequest) req <- mkPipelineFIFOF();
|
|
FIFOF#(DebugResponse) resp <- mkBypassFIFOF();
|
|
FIFOF#(VRAMRequest) mem_req <- mkBypassFIFOF();
|
|
FIFOF#(VRAMResponse) mem_resp <- mkPipelineFIFOF();
|
|
|
|
Reg#(VRAMAddr) next_read_addr <- mkReg(0);
|
|
Reg#(UInt#(8)) read_issue_cnt <- mkReg(0);
|
|
Reg#(UInt#(8)) read_resp_cnt <- mkReg(0);
|
|
|
|
function Bool busy();
|
|
return read_resp_cnt != 0;
|
|
endfunction
|
|
|
|
rule start_req (req.notEmpty && !busy());
|
|
case (req.first) matches
|
|
tagged Ping: begin
|
|
req.deq();
|
|
resp.enq(DebugResponse{data: 0});
|
|
end
|
|
tagged MemByteOp .op: begin
|
|
mem_req.enq(op);
|
|
req.deq();
|
|
if (op.data matches tagged Invalid)
|
|
read_resp_cnt <= 1;
|
|
end
|
|
tagged ReadRange .range: begin
|
|
mem_req.enq(VRAMRequest{
|
|
addr: range.start,
|
|
data: tagged Invalid
|
|
});
|
|
req.deq();
|
|
next_read_addr <= range.start+1;
|
|
read_issue_cnt <= range.count-1;
|
|
read_resp_cnt <= range.count;
|
|
end
|
|
endcase
|
|
endrule
|
|
|
|
rule issue_range (read_issue_cnt > 0);
|
|
mem_req.enq(VRAMRequest{
|
|
addr: next_read_addr,
|
|
data: tagged Invalid
|
|
});
|
|
next_read_addr <= next_read_addr+1;
|
|
read_issue_cnt <= read_issue_cnt-1;
|
|
endrule
|
|
|
|
rule mem_response (mem_resp.notEmpty);
|
|
resp.enq(mem_resp.first);
|
|
mem_resp.deq();
|
|
read_resp_cnt <= read_resp_cnt-1;
|
|
endrule
|
|
|
|
|
|
interface server = toGPServer(req, resp);
|
|
interface vram = toGPClient(mem_req, mem_resp);
|
|
endmodule
|
|
|
|
endpackage
|