debugger/Debugger: add an actual debug module
Introduces a new serial wire format, and a ReadRange command to dump larger chunks of memory at once.
This commit is contained in:
parent
770f515a72
commit
af91f1bda8
|
@ -0,0 +1,180 @@
|
|||
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.
|
||||
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
|
||||
|
||||
(* descending_urgency="mem_response,issue_range,start_req" *)
|
||||
|
||||
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
|
|
@ -0,0 +1,120 @@
|
|||
package Debugger_Test;
|
||||
|
||||
import GetPut::*;
|
||||
import ClientServer::*;
|
||||
import Connectable::*;
|
||||
import StmtFSM::*;
|
||||
import Assert::*;
|
||||
|
||||
import VRAMCore::*;
|
||||
import Debugger::*;
|
||||
import Testing::*;
|
||||
|
||||
module mkTB();
|
||||
let testflags <- mkTestFlags();
|
||||
let cycles <- mkCycleCounter();
|
||||
|
||||
let vram <- mkVRAMCore(4);
|
||||
let dut <- mkDebugger();
|
||||
mkConnection(dut.vram, vram.portA);
|
||||
|
||||
function Action put(DebugRequest req);
|
||||
return action
|
||||
if (testflags.verbose)
|
||||
$display("%0d (%0d): Debugger.put( ", cycles.all, cycles, fshow(req), ")");
|
||||
dut.server.request.put(req);
|
||||
endaction;
|
||||
endfunction
|
||||
|
||||
function ActionValue#(DebugResponse) get();
|
||||
return actionvalue
|
||||
let ret <- dut.server.response.get();
|
||||
if (testflags.verbose)
|
||||
$display("%0d (%0d): Debugger.get() = ", cycles.all, cycles, fshow(ret));
|
||||
return ret;
|
||||
endactionvalue;
|
||||
endfunction
|
||||
|
||||
runTest(200,
|
||||
mkTest("Debugger", seq
|
||||
action
|
||||
put(tagged Ping);
|
||||
cycles.reset();
|
||||
endaction
|
||||
|
||||
action
|
||||
let pong <- get();
|
||||
dynamicAssert(pong == VRAMResponse{data: 0}, "wrong pong response");
|
||||
dynamicAssert(cycles == 1, "wrong debugger delay");
|
||||
endaction
|
||||
|
||||
put(tagged MemByteOp VRAMRequest { addr: 0, data: tagged Valid 0 });
|
||||
put(tagged MemByteOp VRAMRequest { addr: 1, data: tagged Valid 42 });
|
||||
put(tagged MemByteOp VRAMRequest { addr: 2, data: tagged Valid 66 });
|
||||
put(tagged MemByteOp VRAMRequest { addr: 3, data: tagged Valid 91 });
|
||||
put(tagged MemByteOp VRAMRequest { addr: 4, data: tagged Valid 154 });
|
||||
|
||||
action
|
||||
put(tagged MemByteOp VRAMRequest { addr: 1, data: tagged Invalid });
|
||||
cycles.reset();
|
||||
endaction
|
||||
|
||||
action
|
||||
let read <- get();
|
||||
dynamicAssert(read == VRAMResponse{data: 42}, "wrong read response");
|
||||
dynamicAssert(cycles == 3, "wrong debugger delay");
|
||||
endaction
|
||||
|
||||
action
|
||||
put(tagged MemByteOp VRAMRequest { addr: 2, data: tagged Invalid });
|
||||
cycles.reset();
|
||||
endaction
|
||||
|
||||
action
|
||||
let read <- get();
|
||||
dynamicAssert(read == VRAMResponse{data: 66}, "wrong read response");
|
||||
dynamicAssert(cycles == 3, "wrong debugger delay");
|
||||
endaction
|
||||
|
||||
action
|
||||
put(tagged ReadRange ReadRange { start: 0, count: 5 });
|
||||
cycles.reset();
|
||||
endaction
|
||||
|
||||
put(tagged MemByteOp VRAMRequest { addr: 1, data: tagged Invalid });
|
||||
|
||||
action
|
||||
let read <- get();
|
||||
dynamicAssert(read == VRAMResponse{data: 0}, "wrong read response");
|
||||
dynamicAssert(cycles == 3, "wrong debugger delay");
|
||||
endaction
|
||||
action
|
||||
let read <- get();
|
||||
dynamicAssert(read == VRAMResponse{data: 42}, "wrong read response");
|
||||
dynamicAssert(cycles == 4, "wrong debugger delay");
|
||||
endaction
|
||||
action
|
||||
let read <- get();
|
||||
dynamicAssert(read == VRAMResponse{data: 66}, "wrong read response");
|
||||
dynamicAssert(cycles == 5, "wrong debugger delay");
|
||||
endaction
|
||||
action
|
||||
let read <- get();
|
||||
dynamicAssert(read == VRAMResponse{data: 91}, "wrong read response");
|
||||
dynamicAssert(cycles == 6, "wrong debugger delay");
|
||||
endaction
|
||||
action
|
||||
let read <- get();
|
||||
dynamicAssert(read == VRAMResponse{data: 154}, "wrong read response");
|
||||
dynamicAssert(cycles == 7, "wrong debugger delay");
|
||||
endaction
|
||||
// Result of the final byte read
|
||||
action
|
||||
let read <- get();
|
||||
dynamicAssert(read == VRAMResponse{data: 42}, "wrong read response");
|
||||
dynamicAssert(cycles == 10, "wrong debugger delay");
|
||||
endaction
|
||||
endseq));
|
||||
endmodule
|
||||
|
||||
endpackage
|
Loading…
Reference in New Issue