From af91f1bda8c396626df1cee908d1237200c9322a Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 16 Sep 2024 23:23:58 -0700 Subject: [PATCH] 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. --- debugger/Debugger.bsv | 180 +++++++++++++++++++++++++++++++++++++ debugger/Debugger_Test.bsv | 120 +++++++++++++++++++++++++ 2 files changed, 300 insertions(+) create mode 100644 debugger/Debugger.bsv create mode 100644 debugger/Debugger_Test.bsv diff --git a/debugger/Debugger.bsv b/debugger/Debugger.bsv new file mode 100644 index 0000000..21b038d --- /dev/null +++ b/debugger/Debugger.bsv @@ -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 diff --git a/debugger/Debugger_Test.bsv b/debugger/Debugger_Test.bsv new file mode 100644 index 0000000..1e4e2f5 --- /dev/null +++ b/debugger/Debugger_Test.bsv @@ -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