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