Compare commits
9 Commits
e2f4103fdc
...
2156d824c5
Author | SHA1 | Date |
---|---|---|
David Anderson | 2156d824c5 | |
David Anderson | 0d4855fc21 | |
David Anderson | 789d8d002b | |
David Anderson | ee1aecf0b9 | |
David Anderson | 1acec6d835 | |
David Anderson | af91f1bda8 | |
David Anderson | 770f515a72 | |
David Anderson | 700a301468 | |
David Anderson | 8c729698a8 |
|
@ -1,2 +1,3 @@
|
||||||
.envrc
|
.envrc
|
||||||
out/
|
out/
|
||||||
|
log.txt
|
||||||
|
|
|
@ -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.
|
||||||
|
(* 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: 'hEA});
|
||||||
|
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,132 @@
|
||||||
|
package Debugger_Test;
|
||||||
|
|
||||||
|
import GetPut::*;
|
||||||
|
import ClientServer::*;
|
||||||
|
import Connectable::*;
|
||||||
|
import StmtFSM::*;
|
||||||
|
import Assert::*;
|
||||||
|
|
||||||
|
import VRAM::*;
|
||||||
|
import Debugger::*;
|
||||||
|
import Testing::*;
|
||||||
|
|
||||||
|
module mkTB();
|
||||||
|
let testflags <- mkTestFlags();
|
||||||
|
let cycles <- mkCycleCounter();
|
||||||
|
|
||||||
|
let vram <- mkVRAM(4);
|
||||||
|
let dut <- mkDebugger();
|
||||||
|
mkConnection(dut.vram, vram.debugger);
|
||||||
|
|
||||||
|
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: 'hEA}, "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 });
|
||||||
|
put(tagged MemByteOp VRAMRequest { addr: 5, data: tagged Valid 201 });
|
||||||
|
put(tagged MemByteOp VRAMRequest { addr: 6, data: tagged Valid 243 });
|
||||||
|
|
||||||
|
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: 7 });
|
||||||
|
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
|
||||||
|
action
|
||||||
|
let read <- get();
|
||||||
|
dynamicAssert(read == VRAMResponse{data: 201}, "wrong read response");
|
||||||
|
dynamicAssert(cycles == 8, "wrong debugger delay");
|
||||||
|
endaction
|
||||||
|
action
|
||||||
|
let read <- get();
|
||||||
|
dynamicAssert(read == VRAMResponse{data: 243}, "wrong read response");
|
||||||
|
dynamicAssert(cycles == 9, "wrong debugger delay");
|
||||||
|
endaction
|
||||||
|
// Result of the final byte read
|
||||||
|
action
|
||||||
|
let read <- get();
|
||||||
|
dynamicAssert(read == VRAMResponse{data: 42}, "wrong read response");
|
||||||
|
dynamicAssert(cycles == 12, "wrong debugger delay");
|
||||||
|
endaction
|
||||||
|
endseq));
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
endpackage
|
|
@ -56,6 +56,17 @@ func (m *HexView) moveSelection(delta int) bool {
|
||||||
newAddr := m.selectedAddr + delta
|
newAddr := m.selectedAddr + delta
|
||||||
if newAddr >= 0 && newAddr < m.mem.Len() {
|
if newAddr >= 0 && newAddr < m.mem.Len() {
|
||||||
m.selectedAddr = newAddr
|
m.selectedAddr = newAddr
|
||||||
|
start, end := m.VisibleBytes()
|
||||||
|
newFirstLine := newAddr / 16
|
||||||
|
if newAddr < start {
|
||||||
|
newFirstLine = newFirstLine - (m.height * 4 / 5)
|
||||||
|
newFirstLine = max(newFirstLine, 0)
|
||||||
|
m.firstAddr = newFirstLine * 16
|
||||||
|
} else if newAddr >= end {
|
||||||
|
newFirstLine = newFirstLine - (m.height * 1 / 5)
|
||||||
|
newFirstLine = min(newFirstLine, (m.mem.Len()/16)-m.height)
|
||||||
|
m.firstAddr = newFirstLine * 16
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -165,7 +176,7 @@ func (m HexView) updateViewMode(msg tea.Msg) (HexView, tea.Cmd) {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m HexView) View(height int) string {
|
func (m HexView) View() string {
|
||||||
startAddr, endAddr := m.VisibleBytes()
|
startAddr, endAddr := m.VisibleBytes()
|
||||||
bytes := m.mem.Slice(startAddr, endAddr)
|
bytes := m.mem.Slice(startAddr, endAddr)
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ func main() {
|
||||||
}
|
}
|
||||||
defer dbg.Close()
|
defer dbg.Close()
|
||||||
|
|
||||||
mem := memory.New(dbg, 768)
|
mem := memory.New(dbg, 4*1024)
|
||||||
|
|
||||||
if err := UI(mem); err != nil {
|
if err := UI(mem); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
package serial
|
package serial
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.bug.st/serial"
|
"go.bug.st/serial"
|
||||||
)
|
)
|
||||||
|
|
||||||
const portDev = "/dev/ttyUSB0"
|
const (
|
||||||
const maxAddr = 1 << 17
|
portDev = "/dev/ttyUSB0"
|
||||||
|
portSpeed = 115_200
|
||||||
|
maxAddr = 1 << 17
|
||||||
|
pingResponse = 0xEA
|
||||||
|
readTimeout = 2 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
type Serial struct {
|
type Serial struct {
|
||||||
portMu sync.Mutex
|
portMu sync.Mutex
|
||||||
|
@ -25,6 +33,21 @@ func Open() (*Serial, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packet := encode(cmdPing, 0, 0)
|
||||||
|
if _, err := port.Write(packet[:]); err != nil {
|
||||||
|
port.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
port.SetReadTimeout(readTimeout)
|
||||||
|
if _, err := io.ReadFull(port, packet[:1]); err != nil {
|
||||||
|
port.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if packet[0] != pingResponse {
|
||||||
|
port.Close()
|
||||||
|
return nil, fmt.Errorf("wrong ping response: got %x, want %x", packet[0], pingResponse)
|
||||||
|
}
|
||||||
|
|
||||||
return &Serial{port: port}, nil
|
return &Serial{port: port}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,42 +57,71 @@ func (d *Serial) Close() error {
|
||||||
return d.port.Close()
|
return d.port.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func encode(addr int, write bool, data byte) [4]byte {
|
const (
|
||||||
writeVal := uint8(0)
|
cmdReadByte = 1
|
||||||
if write {
|
cmdWriteByte = 2
|
||||||
writeVal = 1
|
cmdReadRange = 3
|
||||||
}
|
cmdPing = 0x7F
|
||||||
|
)
|
||||||
|
|
||||||
|
func encode(cmd int, addr int, data byte) [4]byte {
|
||||||
|
cmd = cmd & (1<<7 - 1)
|
||||||
|
addr = addr & (1<<17 - 1)
|
||||||
|
raw := (uint32(cmd) << 25) + (uint32(addr) << 8) + uint32(data)
|
||||||
var ret [4]byte
|
var ret [4]byte
|
||||||
ret[3] = data
|
binary.BigEndian.PutUint32(ret[:], raw)
|
||||||
ret[2] = byte(addr<<1) | writeVal
|
|
||||||
ret[1] = byte(addr >> 7)
|
|
||||||
ret[0] = byte(addr >> 15)
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Serial) ReadAt(bs []byte, off int64) (int, error) {
|
// readSlow reads memory one byte at a time, and is 10x slower than
|
||||||
|
// burst reading. The code is preserved here just because it's a
|
||||||
|
// simpler implementation in the hardware, and is thus sometimes
|
||||||
|
// useful for debugging.
|
||||||
|
func (d *Serial) readSlow(bs []byte, off int64) (int, error) {
|
||||||
d.portMu.Lock()
|
d.portMu.Lock()
|
||||||
defer d.portMu.Unlock()
|
defer d.portMu.Unlock()
|
||||||
if off+int64(len(bs)) >= maxAddr {
|
if off+int64(len(bs)) >= maxAddr {
|
||||||
return 0, errors.New("OOB read")
|
return 0, errors.New("OOB read")
|
||||||
}
|
}
|
||||||
for i := range bs {
|
for i := range bs {
|
||||||
packet := encode(int(off)+i, false, 0)
|
packet := encode(cmdReadByte, int(off)+i, 0)
|
||||||
if _, err := d.port.Write(packet[:]); err != nil {
|
if _, err := d.port.Write(packet[:]); err != nil {
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
d.port.SetReadTimeout(2 * time.Second)
|
d.port.SetReadTimeout(readTimeout)
|
||||||
n, err := d.port.Read(packet[:1])
|
n, err := io.ReadFull(d.port, bs[i:i+1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return i, err
|
return i + n, err
|
||||||
} else if n == 0 {
|
|
||||||
return i, errors.New("short read")
|
|
||||||
}
|
}
|
||||||
bs[i] = packet[0]
|
|
||||||
}
|
}
|
||||||
return len(bs), nil
|
return len(bs), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Serial) readBurst(bs []byte, off int64) (int, error) {
|
||||||
|
d.portMu.Lock()
|
||||||
|
defer d.portMu.Unlock()
|
||||||
|
if off+int64(len(bs)) >= maxAddr {
|
||||||
|
return 0, errors.New("OOB read")
|
||||||
|
}
|
||||||
|
for i := 0; i < len(bs); i += 255 {
|
||||||
|
burstLen := min(len(bs)-i, 255)
|
||||||
|
packet := encode(cmdReadRange, int(off)+i, byte(burstLen))
|
||||||
|
if _, err := d.port.Write(packet[:]); err != nil {
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
d.port.SetReadTimeout(readTimeout)
|
||||||
|
n, err := io.ReadFull(d.port, bs[i:i+burstLen])
|
||||||
|
if err != nil {
|
||||||
|
return i + n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(bs), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Serial) ReadAt(bs []byte, off int64) (int, error) {
|
||||||
|
return d.readBurst(bs, off)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Serial) WriteAt(bs []byte, off int64) (int, error) {
|
func (d *Serial) WriteAt(bs []byte, off int64) (int, error) {
|
||||||
d.portMu.Lock()
|
d.portMu.Lock()
|
||||||
defer d.portMu.Unlock()
|
defer d.portMu.Unlock()
|
||||||
|
@ -77,7 +129,7 @@ func (d *Serial) WriteAt(bs []byte, off int64) (int, error) {
|
||||||
return 0, errors.New("OOB write")
|
return 0, errors.New("OOB write")
|
||||||
}
|
}
|
||||||
for i, v := range bs {
|
for i, v := range bs {
|
||||||
packet := encode(int(off)+i, true, v)
|
packet := encode(cmdWriteByte, int(off)+i, v)
|
||||||
if _, err := d.port.Write(packet[:]); err != nil {
|
if _, err := d.port.Write(packet[:]); err != nil {
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"git.sentinel65x.com/dave/gary/debugger/memory"
|
"git.sentinel65x.com/dave/gary/debugger/memory"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
@ -14,7 +13,7 @@ func UI(mem *memory.Memory) error {
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
mem: mem,
|
mem: mem,
|
||||||
bottomMsg: "Loading...",
|
statusMsg: "Loading...",
|
||||||
}
|
}
|
||||||
initial.hex = NewHexView(mem, initial.commitWrites)
|
initial.hex = NewHexView(mem, initial.commitWrites)
|
||||||
initial.hex.AddrStyle = text
|
initial.hex.AddrStyle = text
|
||||||
|
@ -31,11 +30,19 @@ func UI(mem *memory.Memory) error {
|
||||||
var (
|
var (
|
||||||
amber = lip.Color("#ffb00")
|
amber = lip.Color("#ffb00")
|
||||||
slate = lip.Color("235")
|
slate = lip.Color("235")
|
||||||
text = lip.NewStyle().Foreground(amber).Background(slate)
|
text = lip.NewStyle().
|
||||||
faintText = text.Faint(true)
|
Foreground(amber).
|
||||||
box = text.Border(lip.NormalBorder(), true, true, true, true).
|
Background(slate).
|
||||||
BorderForeground(amber).
|
BorderForeground(amber).
|
||||||
BorderBackground(slate)
|
BorderBackground(slate).
|
||||||
|
Border(lip.NormalBorder(), false, false, false, false)
|
||||||
|
faintText = text.Faint(true)
|
||||||
|
|
||||||
|
box = lip.NewStyle().
|
||||||
|
Border(lip.NormalBorder()).
|
||||||
|
BorderForeground(amber).
|
||||||
|
BorderBackground(slate).
|
||||||
|
Padding(1, 2)
|
||||||
)
|
)
|
||||||
|
|
||||||
type msgMemoryChanged struct {
|
type msgMemoryChanged struct {
|
||||||
|
@ -56,27 +63,34 @@ type debugger struct {
|
||||||
mem *memory.Memory
|
mem *memory.Memory
|
||||||
hex HexView
|
hex HexView
|
||||||
lastErr error
|
lastErr error
|
||||||
bottomMsg string
|
statusMsg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m debugger) Init() tea.Cmd {
|
func (m debugger) Init() tea.Cmd {
|
||||||
return tea.Sequence(
|
loads := m.readFullMemoryCmds()
|
||||||
m.readFullMemory(),
|
ret := make([]tea.Cmd, 0, (len(loads)*2)+1)
|
||||||
staticMsg(msgUpdateStatus{"Initial load complete."}),
|
for i, c := range loads {
|
||||||
)
|
ret = append(ret, staticMsg(msgUpdateStatus{fmt.Sprintf("Loading block %d/%d", i+1, len(loads))}), c)
|
||||||
|
}
|
||||||
|
ret = append(ret, staticMsg(msgUpdateStatus{"Load complete."}))
|
||||||
|
return tea.Sequence(ret...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m debugger) readFullMemory() tea.Cmd {
|
func (m debugger) readFullMemory() tea.Cmd {
|
||||||
|
return tea.Sequence(m.readFullMemoryCmds()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m debugger) readFullMemoryCmds() []tea.Cmd {
|
||||||
var ret []tea.Cmd
|
var ret []tea.Cmd
|
||||||
for i := 0; i < m.mem.Len(); i += 128 {
|
for i := 0; i < m.mem.Len(); i += 128 {
|
||||||
ret = append(ret, m.readMemory(i, 128))
|
ret = append(ret, m.readMemory(i, 128))
|
||||||
}
|
}
|
||||||
return tea.Sequence(ret...)
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m debugger) readMemory(start, count int) tea.Cmd {
|
func (m debugger) readMemory(start, count int) tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
if err := m.mem.Load(start, count); err != nil {
|
if err := m.mem.Refresh(start, count); err != nil {
|
||||||
return msgErr{fmt.Errorf("loading region 0x%x+%x: %w", start, count, err)}
|
return msgErr{fmt.Errorf("loading region 0x%x+%x: %w", start, count, err)}
|
||||||
}
|
}
|
||||||
return msgMemoryChanged{start, count}
|
return msgMemoryChanged{start, count}
|
||||||
|
@ -103,13 +117,12 @@ func (m debugger) commitWrites() tea.Cmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m debugger) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m debugger) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
log.Printf("msv: %#v", msg)
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.WindowSizeMsg:
|
case tea.WindowSizeMsg:
|
||||||
m.width = msg.Width
|
m.width = msg.Width
|
||||||
m.height = msg.Height
|
m.height = msg.Height
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
m.hex, cmd = m.hex.Update(HexViewSetHeight{m.height - 6}) // TODO: make less shit
|
m.hex, cmd = m.hex.Update(HexViewSetHeight{m.height - box.GetVerticalFrameSize() - 1})
|
||||||
return m, cmd
|
return m, cmd
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
|
@ -118,13 +131,14 @@ func (m debugger) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
case "r":
|
case "r":
|
||||||
start, end := m.hex.VisibleBytes()
|
start, end := m.hex.VisibleBytes()
|
||||||
return m, m.readMemory(start, end-start)
|
return m, m.readMemory(start, end-start)
|
||||||
|
case "R":
|
||||||
|
return m, m.readFullMemory()
|
||||||
}
|
}
|
||||||
case msgErr:
|
case msgErr:
|
||||||
log.Print(msg.err)
|
|
||||||
m.lastErr = msg.err
|
m.lastErr = msg.err
|
||||||
return m, nil
|
return m, nil
|
||||||
case msgUpdateStatus:
|
case msgUpdateStatus:
|
||||||
m.bottomMsg = msg.status
|
m.statusMsg = msg.status
|
||||||
}
|
}
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
m.hex, cmd = m.hex.Update(msg)
|
m.hex, cmd = m.hex.Update(msg)
|
||||||
|
@ -158,20 +172,23 @@ func (m debugger) View() string {
|
||||||
return lip.Place(m.width, m.height, lip.Center, lip.Center, "Please embiggen your terminal")
|
return lip.Place(m.width, m.height, lip.Center, lip.Center, "Please embiggen your terminal")
|
||||||
}
|
}
|
||||||
|
|
||||||
statusBar := text.Width(m.width).Height(1).Align(lip.Center)
|
hex := box.Render(m.hex.View())
|
||||||
topSegment := statusBar.Width(m.width / 3).Reverse(true)
|
|
||||||
|
|
||||||
hexBox := lip.NewStyle().Border(lip.NormalBorder()).BorderForeground(amber).BorderBackground(slate).Padding(1, 2)
|
barText := text.Reverse(true) //.Height(1)
|
||||||
hexHeight := m.height - 2 - hexBox.GetVerticalFrameSize()
|
|
||||||
hex := hexBox.Render(m.hex.View(hexHeight))
|
|
||||||
|
|
||||||
topLeft := text.Reverse(true).Bold(true).Render("Addr: ") + text.Reverse(true).Render(fmt.Sprintf("0x%-5x", m.hex.SelectedAddr()))
|
topLeft := barText.Bold(true).PaddingLeft(1).Render("Addr: ")
|
||||||
topLeft = topSegment.Padding(0, 1).Align(lip.Left).Render(topLeft)
|
topLeft += barText.Render(fmt.Sprintf("0x%x (%d)", m.hex.SelectedAddr(), m.hex.SelectedAddr()))
|
||||||
topMid := topSegment.Bold(true).Render("GARY Debugger")
|
topRight := barText.PaddingRight(1).Render(m.statusMsg)
|
||||||
topRight := topSegment.Render("")
|
title := barText.Padding(0, 1).Bold(true).Render("GARY Debugger")
|
||||||
topStatus := lip.JoinHorizontal(lip.Top, topLeft, topMid, topRight)
|
|
||||||
bottomStatus := statusBar.Render(m.bottomMsg)
|
|
||||||
|
|
||||||
top := lip.JoinVertical(lip.Center, topStatus, hex, bottomStatus)
|
sideWidth := max(lip.Width(topLeft), lip.Width(topRight))
|
||||||
return top
|
titleWidth := m.width - 2*sideWidth
|
||||||
|
topLeft = barText.Width(sideWidth).Align(lip.Left).Render(topLeft)
|
||||||
|
topRight = barText.Width(sideWidth).Align(lip.Right).Render(topRight)
|
||||||
|
title = barText.Width(titleWidth).Align(lip.Center).Render(title)
|
||||||
|
|
||||||
|
status := lip.JoinHorizontal(lip.Top, topLeft, title, topRight)
|
||||||
|
|
||||||
|
all := lip.JoinVertical(lip.Center, status, hex)
|
||||||
|
return all
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,18 +8,22 @@ import Blinky::*;
|
||||||
import PackUnpack::*;
|
import PackUnpack::*;
|
||||||
import UART::*;
|
import UART::*;
|
||||||
import VRAM::*;
|
import VRAM::*;
|
||||||
|
import Debugger::*;
|
||||||
|
|
||||||
module mkUARTDebugger(Integer clock_frequency, Integer uart_bitrate, VRAMServer mem, UART_PHY ifc);
|
module mkUARTDebugger(Integer clock_frequency, Integer uart_bitrate, VRAMServer mem, UART_PHY ifc);
|
||||||
UART uart <- mkUART(clock_frequency, uart_bitrate);
|
UART uart <- mkUART(clock_frequency, uart_bitrate);
|
||||||
disableFlowControl(uart); // Can't do hardware flow control on ULX3S
|
disableFlowControl(uart); // Can't do hardware flow control on ULX3S
|
||||||
|
let uart_client = toGPClient(uart.receive, uart.send);
|
||||||
|
|
||||||
Server#(Bit#(8), VRAMRequest) decode <- mkUnpacker();
|
Server#(Bit#(8), DebugRequest) decode <- mkUnpacker();
|
||||||
Server#(VRAMResponse, Bit#(8)) encode <- mkPacker();
|
Server#(DebugResponse, Bit#(8)) encode <- mkPacker();
|
||||||
|
let bytes_server = toGPServer(decode.request, encode.response);
|
||||||
|
let debug_client = toGPClient(decode.response, encode.request);
|
||||||
|
mkConnection(uart_client, bytes_server);
|
||||||
|
|
||||||
mkConnection(uart.receive, decode.request);
|
let debug <- mkDebugger();
|
||||||
mkConnection(decode.response, mem.request);
|
mkConnection(debug_client, debug.server);
|
||||||
mkConnection(mem.response, encode.request);
|
mkConnection(debug.vram, mem);
|
||||||
mkConnection(encode.response, uart.send);
|
|
||||||
|
|
||||||
return uart.phy;
|
return uart.phy;
|
||||||
endmodule
|
endmodule
|
||||||
|
@ -38,7 +42,7 @@ endinterface
|
||||||
module mkTop(Top);
|
module mkTop(Top);
|
||||||
////////////
|
////////////
|
||||||
// Memory
|
// Memory
|
||||||
VRAM mem <- mkVRAM(4);
|
VRAM mem <- mkVRAM(128);
|
||||||
|
|
||||||
////////////
|
////////////
|
||||||
// Debugging
|
// Debugging
|
||||||
|
|
11
log.txt
11
log.txt
|
@ -1,11 +0,0 @@
|
||||||
2024/09/16 15:45:59 msv: tea.sequenceMsg{(tea.Cmd)(0x50b580), (tea.Cmd)(0x50b380)}
|
|
||||||
2024/09/16 15:45:59 msv: tea.sequenceMsg{(tea.Cmd)(0x50b5c0), (tea.Cmd)(0x50b5c0), (tea.Cmd)(0x50b5c0), (tea.Cmd)(0x50b5c0), (tea.Cmd)(0x50b5c0), (tea.Cmd)(0x50b5c0)}
|
|
||||||
2024/09/16 15:45:59 msv: tea.WindowSizeMsg{Width:141, Height:39}
|
|
||||||
2024/09/16 15:45:59 msv: main.msgUpdateStatus{status:"Initial load complete."}
|
|
||||||
2024/09/16 15:46:00 msv: main.msgMemoryChanged{start:0, count:128}
|
|
||||||
2024/09/16 15:46:00 msv: main.msgMemoryChanged{start:128, count:128}
|
|
||||||
2024/09/16 15:46:00 msv: main.msgMemoryChanged{start:256, count:128}
|
|
||||||
2024/09/16 15:46:00 msv: main.msgMemoryChanged{start:384, count:128}
|
|
||||||
2024/09/16 15:46:00 msv: main.msgMemoryChanged{start:512, count:128}
|
|
||||||
2024/09/16 15:46:00 msv: main.msgMemoryChanged{start:640, count:128}
|
|
||||||
2024/09/16 15:46:01 msv: tea.KeyMsg{Type:-1, Runes:[]int32{113}, Alt:false, Paste:false}
|
|
|
@ -4,6 +4,8 @@ import GetPut::*;
|
||||||
import ClientServer::*;
|
import ClientServer::*;
|
||||||
import BRAMCore::*;
|
import BRAMCore::*;
|
||||||
import Real::*;
|
import Real::*;
|
||||||
|
import FIFOF::*;
|
||||||
|
import SpecialFIFOs::*;
|
||||||
|
|
||||||
import DelayLine::*;
|
import DelayLine::*;
|
||||||
import ECP5_RAM::*;
|
import ECP5_RAM::*;
|
||||||
|
@ -21,9 +23,15 @@ typedef UInt#(17) VRAMAddr;
|
||||||
typedef UInt#(2) ArrayAddr;
|
typedef UInt#(2) ArrayAddr;
|
||||||
typedef UInt#(3) ChipAddr;
|
typedef UInt#(3) ChipAddr;
|
||||||
typedef UInt#(12) ByteAddr;
|
typedef UInt#(12) ByteAddr;
|
||||||
|
typedef struct {
|
||||||
|
ChipAddr chip;
|
||||||
|
ByteAddr addr;
|
||||||
|
} EBRAddr deriving (Bits, Eq, FShow);
|
||||||
|
|
||||||
// ByteRAM is two EBRs glued together to make a whole-byte memory.
|
typedef struct {
|
||||||
typedef EBR#(ByteAddr, VRAMData, ByteAddr, VRAMData) ByteRAM;
|
EBRAddr addr;
|
||||||
|
Maybe#(data) data;
|
||||||
|
} VRAMInternalRequest#(type data) deriving (Bits, Eq, FShow);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
VRAMAddr addr;
|
VRAMAddr addr;
|
||||||
|
@ -34,6 +42,11 @@ typedef struct {
|
||||||
VRAMData data;
|
VRAMData data;
|
||||||
} VRAMResponse deriving (Bits, Eq, FShow);
|
} VRAMResponse deriving (Bits, Eq, FShow);
|
||||||
|
|
||||||
|
interface VRAMCoreInternal#(type data);
|
||||||
|
interface Server#(VRAMInternalRequest#(data), data) portA;
|
||||||
|
interface Server#(VRAMInternalRequest#(data), data) portB;
|
||||||
|
endinterface
|
||||||
|
|
||||||
module mkNibbleRAM_ECP5(ChipAddr chip_addr, EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) ifc);
|
module mkNibbleRAM_ECP5(ChipAddr chip_addr, EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) ifc);
|
||||||
EBRPortConfig cfg = defaultValue;
|
EBRPortConfig cfg = defaultValue;
|
||||||
cfg.chip_select_addr = chip_addr;
|
cfg.chip_select_addr = chip_addr;
|
||||||
|
@ -61,96 +74,159 @@ module mkNibbleRAM_Sim(ChipAddr chip_addr, EBR#(ByteAddr, Bit#(4), ByteAddr, Bit
|
||||||
endinterface
|
endinterface
|
||||||
endmodule
|
endmodule
|
||||||
|
|
||||||
module mkNibbleRAM(ChipAddr chip_addr, EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) ifc);
|
module mkNibbleRAM(ChipAddr chip_addr, VRAMCoreInternal#(Bit#(4)) ifc);
|
||||||
let _ret;
|
let _ret;
|
||||||
if (genC())
|
if (genC())
|
||||||
_ret <- mkNibbleRAM_Sim(chip_addr);
|
_ret <- mkNibbleRAM_Sim(chip_addr);
|
||||||
else
|
else
|
||||||
_ret <- mkNibbleRAM_ECP5(chip_addr);
|
_ret <- mkNibbleRAM_ECP5(chip_addr);
|
||||||
return _ret;
|
|
||||||
|
interface Server portA;
|
||||||
|
interface Put request;
|
||||||
|
method Action put(req);
|
||||||
|
_ret.portA.put(req.addr.chip, isValid(req.data), req.addr.addr, fromMaybe(0, req.data));
|
||||||
|
endmethod
|
||||||
|
endinterface
|
||||||
|
interface Get response;
|
||||||
|
method ActionValue#(Bit#(4)) get();
|
||||||
|
return _ret.portA.read();
|
||||||
|
endmethod
|
||||||
|
endinterface
|
||||||
|
endinterface
|
||||||
|
|
||||||
|
interface Server portB;
|
||||||
|
interface Put request;
|
||||||
|
method Action put(req);
|
||||||
|
_ret.portB.put(req.addr.chip, isValid(req.data), req.addr.addr, fromMaybe(0, req.data));
|
||||||
|
endmethod
|
||||||
|
endinterface
|
||||||
|
interface Get response;
|
||||||
|
method ActionValue#(Bit#(4)) get();
|
||||||
|
return _ret.portB.read();
|
||||||
|
endmethod
|
||||||
|
endinterface
|
||||||
|
endinterface
|
||||||
endmodule
|
endmodule
|
||||||
|
|
||||||
// mkByteRAM glues two ECP5 EBRs together to make a 4096x8b memory
|
// mkByteRAM glues two ECP5 EBRs together to make a 4096x8b memory
|
||||||
// block. Like the underlying ECP5 EBRs, callers must bring their own
|
// block. Like the underlying ECP5 EBRs, callers must bring their own
|
||||||
// flow control to read out responses one cycle after putting a read
|
// flow control to read out responses one cycle after putting a read
|
||||||
// request.
|
// request.
|
||||||
module mkByteRAM(ChipAddr chip_addr, ByteRAM ifc);
|
module mkByteRAM(ChipAddr chip_addr, VRAMCoreInternal#(VRAMData) ifc);
|
||||||
EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) upper <- mkNibbleRAM(chip_addr);
|
VRAMCoreInternal#(Bit#(4)) upper <- mkNibbleRAM(chip_addr);
|
||||||
EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) lower <- mkNibbleRAM(chip_addr);
|
VRAMCoreInternal#(Bit#(4)) lower <- mkNibbleRAM(chip_addr);
|
||||||
|
|
||||||
interface EBRPort portA;
|
interface Server portA;
|
||||||
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
|
interface Put request;
|
||||||
upper.portA.put(chip_select, write, addr, truncate(data_in>>4));
|
method Action put(req);
|
||||||
lower.portA.put(chip_select, write, addr, truncate(data_in));
|
Maybe#(Bit#(4)) ud = tagged Invalid;
|
||||||
endmethod
|
Maybe#(Bit#(4)) ld = tagged Invalid;
|
||||||
|
if (req.data matches tagged Valid .data) begin
|
||||||
method VRAMData read();
|
ud = tagged Valid data[7:4];
|
||||||
return (extend(upper.portA.read())<<4) | (extend(lower.portA.read()));
|
ld = tagged Valid data[3:0];
|
||||||
|
end
|
||||||
|
upper.portA.request.put(VRAMInternalRequest{
|
||||||
|
addr: req.addr,
|
||||||
|
data: ud
|
||||||
|
});
|
||||||
|
lower.portA.request.put(VRAMInternalRequest{
|
||||||
|
addr: req.addr,
|
||||||
|
data: ld
|
||||||
|
});
|
||||||
endmethod
|
endmethod
|
||||||
endinterface
|
endinterface
|
||||||
|
interface Get response;
|
||||||
interface EBRPort portB;
|
method ActionValue#(VRAMData) get();
|
||||||
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
|
let u <- upper.portA.response.get();
|
||||||
upper.portB.put(chip_select, write, addr, truncate(data_in>>4));
|
let l <- lower.portA.response.get();
|
||||||
lower.portB.put(chip_select, write, addr, truncate(data_in));
|
return {u, l};
|
||||||
endmethod
|
endmethod
|
||||||
|
endinterface
|
||||||
|
endinterface
|
||||||
|
|
||||||
method VRAMData read();
|
interface Server portB;
|
||||||
return (extend(upper.portB.read())<<4) | (extend(lower.portB.read()));
|
interface Put request;
|
||||||
|
method Action put(req);
|
||||||
|
Maybe#(Bit#(4)) ud = tagged Invalid;
|
||||||
|
Maybe#(Bit#(4)) ld = tagged Invalid;
|
||||||
|
if (req.data matches tagged Valid .data) begin
|
||||||
|
ud = tagged Valid data[7:4];
|
||||||
|
ld = tagged Valid data[3:0];
|
||||||
|
end
|
||||||
|
upper.portB.request.put(VRAMInternalRequest{
|
||||||
|
addr: req.addr,
|
||||||
|
data: ud
|
||||||
|
});
|
||||||
|
lower.portB.request.put(VRAMInternalRequest{
|
||||||
|
addr: req.addr,
|
||||||
|
data: ld
|
||||||
|
});
|
||||||
endmethod
|
endmethod
|
||||||
endinterface
|
endinterface
|
||||||
|
interface Get response;
|
||||||
|
method ActionValue#(VRAMData) get();
|
||||||
|
let u <- upper.portB.response.get();
|
||||||
|
let l <- lower.portB.response.get();
|
||||||
|
return {u, l};
|
||||||
|
endmethod
|
||||||
|
endinterface
|
||||||
|
endinterface
|
||||||
endmodule : mkByteRAM
|
endmodule : mkByteRAM
|
||||||
|
|
||||||
// mkByteRAMArray arrays up to 8 mkByteRAMs together, using the
|
// mkByteRAMArray arrays up to 8 mkByteRAMs together, using the
|
||||||
// hardwired chip select lines to route inputs appropriately and a mux
|
// hardwired chip select lines to route inputs appropriately and a mux
|
||||||
// tree to collect outputs. With num_chips=8, the resulting ByteRAM is
|
// tree to collect outputs. With num_chips=8, the resulting ByteRAM is
|
||||||
// 32768x8b.
|
// 32768x8b.
|
||||||
module mkByteRAMArray(Integer num_chips, ByteRAM ifc);
|
//
|
||||||
|
// The returned ByteRAM _does_ provide flow control: both read() and
|
||||||
|
// put() are guarded. If reads are consumed as soon as they're
|
||||||
|
// available, the RAM can process a put() every cycle.
|
||||||
|
module mkByteRAMArray(Integer num_chips, VRAMCoreInternal#(VRAMData) ifc);
|
||||||
if (num_chips > 8)
|
if (num_chips > 8)
|
||||||
error("mkByteRAMArray can only array 8 raw ByteRAMs");
|
error("mkByteRAMArray can only array 8 raw ByteRAMs");
|
||||||
|
|
||||||
ByteRAM blocks[num_chips];
|
VRAMCoreInternal#(VRAMData) blocks[num_chips];
|
||||||
for (Integer i=0; i<num_chips; i=i+1)
|
for (Integer i=0; i<num_chips; i=i+1)
|
||||||
blocks[i] <- mkByteRAM(fromInteger(i));
|
blocks[i] <- mkByteRAM(fromInteger(i));
|
||||||
|
|
||||||
DelayLine#(ChipAddr) read_chip_A <- mkDelayLine(1);
|
FIFOF#(ChipAddr) read_chip_A <- mkPipelineFIFOF();
|
||||||
DelayLine#(ChipAddr) read_chip_B <- mkDelayLine(1);
|
FIFOF#(ChipAddr) read_chip_B <- mkPipelineFIFOF();
|
||||||
|
|
||||||
interface EBRPort portA;
|
interface Server portA;
|
||||||
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
|
interface Put request;
|
||||||
|
method Action put(req) if (read_chip_A.notFull);
|
||||||
for (Integer i=0; i<num_chips; i=i+1)
|
for (Integer i=0; i<num_chips; i=i+1)
|
||||||
blocks[i].portA.put(chip_select, write, addr, data_in);
|
blocks[i].portA.request.put(req);
|
||||||
if (!write)
|
if (!isValid(req.data))
|
||||||
read_chip_A <= chip_select;
|
read_chip_A.enq(req.addr.chip);
|
||||||
endmethod
|
|
||||||
method VRAMData read();
|
|
||||||
if (read_chip_A.ready)
|
|
||||||
if (read_chip_A <= fromInteger(num_chips-1))
|
|
||||||
return blocks[read_chip_A].portA.read();
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
endmethod
|
endmethod
|
||||||
endinterface
|
endinterface
|
||||||
|
interface Get response;
|
||||||
|
method ActionValue#(VRAMData) get();
|
||||||
|
read_chip_A.deq();
|
||||||
|
let res <- blocks[read_chip_A.first].portA.response.get();
|
||||||
|
return res;
|
||||||
|
endmethod
|
||||||
|
endinterface
|
||||||
|
endinterface
|
||||||
|
|
||||||
interface EBRPort portB;
|
interface Server portB;
|
||||||
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
|
interface Put request;
|
||||||
|
method Action put(req) if (read_chip_B.notFull);
|
||||||
for (Integer i=0; i<num_chips; i=i+1)
|
for (Integer i=0; i<num_chips; i=i+1)
|
||||||
blocks[i].portB.put(chip_select, write, addr, data_in);
|
blocks[i].portB.request.put(req);
|
||||||
if (!write)
|
if (!isValid(req.data))
|
||||||
read_chip_B <= chip_select;
|
read_chip_B.enq(req.addr.chip);
|
||||||
endmethod
|
endmethod
|
||||||
method VRAMData read();
|
endinterface
|
||||||
if (read_chip_B.ready)
|
interface Get response;
|
||||||
if (read_chip_B <= fromInteger(num_chips-1))
|
method ActionValue#(VRAMData) get();
|
||||||
return blocks[read_chip_B].portB.read();
|
read_chip_B.deq();
|
||||||
else
|
let res <- blocks[read_chip_B.first].portB.response.get();
|
||||||
return 0;
|
return res;
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
endmethod
|
endmethod
|
||||||
endinterface
|
endinterface
|
||||||
|
endinterface
|
||||||
endmodule
|
endmodule
|
||||||
|
|
||||||
interface VRAMCore;
|
interface VRAMCore;
|
||||||
|
@ -180,49 +256,57 @@ module mkVRAMCore(Integer num_kilobytes, VRAMCore ifc);
|
||||||
let num_byterams = num_bytes/4096;
|
let num_byterams = num_bytes/4096;
|
||||||
let num_arrays = ceil(fromInteger(num_byterams) / 8);
|
let num_arrays = ceil(fromInteger(num_byterams) / 8);
|
||||||
|
|
||||||
function Tuple3#(ArrayAddr, ChipAddr, ByteAddr) split_addr(VRAMAddr a);
|
function Tuple2#(ArrayAddr, EBRAddr) split_addr(VRAMAddr a);
|
||||||
return unpack(pack(a));
|
return unpack(pack(a));
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
ByteRAM arrays[num_arrays];
|
VRAMCoreInternal#(VRAMData) arrays[num_arrays];
|
||||||
for (Integer i=0; i<num_arrays; i=i+1) begin
|
for (Integer i=0; i<num_arrays; i=i+1) begin
|
||||||
let array_size = min(num_byterams - (i*8), 8);
|
let array_size = min(num_byterams - (i*8), 8);
|
||||||
arrays[i] <- mkByteRAMArray(array_size);
|
arrays[i] <- mkByteRAMArray(array_size);
|
||||||
end
|
end
|
||||||
|
|
||||||
Reg#(Maybe#(ArrayAddr)) inflight_A[2] <- mkCReg(2, tagged Invalid);
|
FIFOF#(ArrayAddr) array_addr_A <- mkPipelineFIFOF();
|
||||||
Reg#(Maybe#(ArrayAddr)) inflight_B[2] <- mkCReg(2, tagged Invalid);
|
FIFOF#(ArrayAddr) array_addr_B <- mkPipelineFIFOF();
|
||||||
|
|
||||||
interface Server portA;
|
interface Server portA;
|
||||||
interface Put request;
|
interface Put request;
|
||||||
method Action put(VRAMRequest req) if (inflight_A[1] matches tagged Invalid);
|
method Action put(req);
|
||||||
match {.array, .chip, .byteaddr} = split_addr(req.addr);
|
match {.array, .addr} = split_addr(req.addr);
|
||||||
arrays[array].portA.put(chip, isValid(req.data), byteaddr, fromMaybe(0, req.data));
|
arrays[array].portA.request.put(VRAMInternalRequest{
|
||||||
|
addr: addr,
|
||||||
|
data: req.data
|
||||||
|
});
|
||||||
if (!isValid(req.data))
|
if (!isValid(req.data))
|
||||||
inflight_A[1] <= tagged Valid array;
|
array_addr_A.enq(array);
|
||||||
endmethod
|
endmethod
|
||||||
endinterface
|
endinterface
|
||||||
interface Get response;
|
interface Get response;
|
||||||
method ActionValue#(VRAMResponse) get() if (inflight_A[0] matches tagged Valid .array);
|
method ActionValue#(VRAMResponse) get();
|
||||||
inflight_A[0] <= tagged Invalid;
|
array_addr_A.deq();
|
||||||
return VRAMResponse{data: arrays[array].portA.read()};
|
let ret <- arrays[array_addr_A.first].portA.response.get();
|
||||||
|
return VRAMResponse{data: ret};
|
||||||
endmethod
|
endmethod
|
||||||
endinterface
|
endinterface
|
||||||
endinterface
|
endinterface
|
||||||
|
|
||||||
interface Server portB;
|
interface Server portB;
|
||||||
interface Put request;
|
interface Put request;
|
||||||
method Action put(VRAMRequest req) if (inflight_B[1] matches tagged Invalid);
|
method Action put(req);
|
||||||
match {.array, .chip, .byteaddr} = split_addr(req.addr);
|
match {.array, .addr} = split_addr(req.addr);
|
||||||
arrays[array].portB.put(0, isValid(req.data), byteaddr, fromMaybe(0, req.data));
|
arrays[array].portB.request.put(VRAMInternalRequest{
|
||||||
|
addr: addr,
|
||||||
|
data: req.data
|
||||||
|
});
|
||||||
if (!isValid(req.data))
|
if (!isValid(req.data))
|
||||||
inflight_B[1] <= tagged Valid array;
|
array_addr_B.enq(array);
|
||||||
endmethod
|
endmethod
|
||||||
endinterface
|
endinterface
|
||||||
interface Get response;
|
interface Get response;
|
||||||
method ActionValue#(VRAMResponse) get() if (inflight_B[0] matches tagged Valid .array);
|
method ActionValue#(VRAMResponse) get();
|
||||||
inflight_B[0] <= tagged Invalid;
|
array_addr_B.deq();
|
||||||
return VRAMResponse{data: arrays[array].portB.read()};
|
let ret <- arrays[array_addr_B.first].portB.response.get();
|
||||||
|
return VRAMResponse{data: ret};
|
||||||
endmethod
|
endmethod
|
||||||
endinterface
|
endinterface
|
||||||
endinterface
|
endinterface
|
||||||
|
|
|
@ -21,40 +21,46 @@ function ActionValue#(Bool) verbose();
|
||||||
endactionvalue);
|
endactionvalue);
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
typedef (function ActionValue#(Bit#(8)) next()) ValFn;
|
module mkConstantValue(Integer cnst, Get#(Bit#(8)) ifc);
|
||||||
|
method ActionValue#(Bit#(8)) get();
|
||||||
function ValFn constant_value(Integer cnst);
|
|
||||||
function ActionValue#(Bit#(8)) next();
|
|
||||||
return (actionvalue
|
|
||||||
return fromInteger(cnst);
|
return fromInteger(cnst);
|
||||||
endactionvalue);
|
endmethod
|
||||||
endfunction
|
endmodule
|
||||||
|
|
||||||
return next;
|
module mkIncrementingValue(Get#(Bit#(8)));
|
||||||
endfunction
|
|
||||||
|
|
||||||
module mkIncrementingValue(ValFn);
|
|
||||||
Reg#(Bit#(8)) val <- mkReg(0);
|
Reg#(Bit#(8)) val <- mkReg(0);
|
||||||
|
|
||||||
function ActionValue#(Bit#(8)) next();
|
method ActionValue#(Bit#(8)) get();
|
||||||
return (actionvalue
|
// Cycle through 101 values. 101 is prime, so the pattern it
|
||||||
// Cycle through 101 values. 101 is prime, so the
|
// generates doesn't align to a power of two and should detect
|
||||||
// pattern it generates doesn't align to a power of
|
// any memory mapping errors.
|
||||||
// two and should detect any memory mapping errors.
|
|
||||||
if (val == 100)
|
if (val == 100)
|
||||||
val <= 0;
|
val <= 0;
|
||||||
else
|
else
|
||||||
val <= val+1;
|
val <= val+1;
|
||||||
// Add another number to get all nonzero values, to
|
|
||||||
// detect writes that don't stick.
|
|
||||||
return 23+val;
|
|
||||||
endactionvalue);
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
return next;
|
// Add another number to get all nonzero values, to detect
|
||||||
|
// writes that don't stick.
|
||||||
|
return 23+val;
|
||||||
|
endmethod
|
||||||
endmodule
|
endmodule
|
||||||
|
|
||||||
module mkWriter(Server#(VRAMRequest, VRAMResponse) dut, ValFn next_value, Machine ifc);
|
module mkSlowReader(Get#(Bit#(8)) inner, Get#(Bit#(8)) ifc);
|
||||||
|
Reg#(Bool) delay <- mkReg(True);
|
||||||
|
|
||||||
|
(* no_implicit_conditions,fire_when_enabled *)
|
||||||
|
rule clear_delay (delay);
|
||||||
|
delay <= False;
|
||||||
|
endrule
|
||||||
|
|
||||||
|
method ActionValue#(Bit#(8)) get() if (!delay);
|
||||||
|
delay <= True;
|
||||||
|
let ret <- inner.get();
|
||||||
|
return ret;
|
||||||
|
endmethod
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module mkWriter(Server#(VRAMRequest, VRAMResponse) dut, Get#(Bit#(8)) next_value, Machine ifc);
|
||||||
let flags <- mkTestFlags();
|
let flags <- mkTestFlags();
|
||||||
let cycles <- mkCycleCounter();
|
let cycles <- mkCycleCounter();
|
||||||
let write_cycle_time <- mkCycleCounter();
|
let write_cycle_time <- mkCycleCounter();
|
||||||
|
@ -67,7 +73,7 @@ module mkWriter(Server#(VRAMRequest, VRAMResponse) dut, ValFn next_value, Machin
|
||||||
dynamicAssert(write_cycle_time == 1, "write didn't happen every cycle");
|
dynamicAssert(write_cycle_time == 1, "write didn't happen every cycle");
|
||||||
write_cycle_time.reset();
|
write_cycle_time.reset();
|
||||||
|
|
||||||
let data <- next_value();
|
let data <- next_value.get();
|
||||||
let req = VRAMRequest{
|
let req = VRAMRequest{
|
||||||
addr: idx,
|
addr: idx,
|
||||||
data: tagged Valid data
|
data: tagged Valid data
|
||||||
|
@ -96,7 +102,7 @@ module mkWriter(Server#(VRAMRequest, VRAMResponse) dut, ValFn next_value, Machin
|
||||||
endmethod
|
endmethod
|
||||||
endmodule
|
endmodule
|
||||||
|
|
||||||
module mkReader(Server#(VRAMRequest, VRAMResponse) dut, ValFn next_value, Machine ifc);
|
module mkReader(Server#(VRAMRequest, VRAMResponse) dut, Get#(Bit#(8)) next_value, Machine ifc);
|
||||||
let flags <- mkTestFlags();
|
let flags <- mkTestFlags();
|
||||||
let cycles <- mkCycleCounter();
|
let cycles <- mkCycleCounter();
|
||||||
|
|
||||||
|
@ -126,11 +132,11 @@ module mkReader(Server#(VRAMRequest, VRAMResponse) dut, ValFn next_value, Machin
|
||||||
|
|
||||||
rule verify_read (verify_remaining > 0);
|
rule verify_read (verify_remaining > 0);
|
||||||
let got <- dut.response.get();
|
let got <- dut.response.get();
|
||||||
let want <- next_value();
|
let want <- next_value.get();
|
||||||
dynamicAssert(got.data == want, "wrong value seen during read");
|
|
||||||
|
|
||||||
if (flags.verbose)
|
if (flags.verbose)
|
||||||
$display("%0d: verify_read(%0d) = %0d, want %0d", cycles.all, verify_idx, got, want);
|
$display("%0d: verify_read(%0d) = %0d, want %0d", cycles.all, verify_idx, got, want);
|
||||||
|
dynamicAssert(got.data == want, "wrong value seen during read");
|
||||||
|
|
||||||
if (verify_remaining == 1)
|
if (verify_remaining == 1)
|
||||||
$display("Verified %0d reads in %0d cycles", total, cycles);
|
$display("Verified %0d reads in %0d cycles", total, cycles);
|
||||||
|
@ -187,17 +193,36 @@ module mkTwoPortTest(VRAMCore dut, Stmt ret);
|
||||||
endseq);
|
endseq);
|
||||||
endmodule
|
endmodule
|
||||||
|
|
||||||
|
module mkSlowConsumerTest(VRAMCore dut, Stmt ret);
|
||||||
|
let winc <- mkIncrementingValue();
|
||||||
|
let writer <- mkWriter(dut.portA, winc);
|
||||||
|
|
||||||
|
let rinc <- mkIncrementingValue();
|
||||||
|
let rinc_slow <- mkSlowReader(rinc);
|
||||||
|
let reader <- mkReader(dut.portA, rinc_slow);
|
||||||
|
|
||||||
|
return (seq
|
||||||
|
writer.start(3000, 6000);
|
||||||
|
await(writer.done);
|
||||||
|
|
||||||
|
reader.start(3000, 6000);
|
||||||
|
await(reader.done);
|
||||||
|
endseq);
|
||||||
|
endmodule
|
||||||
|
|
||||||
(* descending_urgency="simple.reader.issue_read,two_port.writer.write" *)
|
(* descending_urgency="simple.reader.issue_read,two_port.writer.write" *)
|
||||||
module mkTB(Empty);
|
module mkTB(Empty);
|
||||||
let dut <- mkVRAMCore(112);
|
let dut <- mkVRAMCore(112);
|
||||||
|
|
||||||
let simple <- mkSimpleTest(dut);
|
let simple <- mkSimpleTest(dut);
|
||||||
let two_port <- mkTwoPortTest(dut);
|
let two_port <- mkTwoPortTest(dut);
|
||||||
|
let slow_reader <- mkSlowConsumerTest(dut);
|
||||||
|
|
||||||
runTest(100000,
|
runTest(100000,
|
||||||
mkTest("VRAMCore", seq
|
mkTest("VRAMCore", seq
|
||||||
mkTest("VRAMCore/simple", simple);
|
//mkTest("VRAMCore/simple", simple);
|
||||||
mkTest("VRAMCore/two_port", two_port);
|
//mkTest("VRAMCore/two_port", two_port);
|
||||||
|
mkTest("VRAMCore/slow_reader", slow_reader);
|
||||||
endseq));
|
endseq));
|
||||||
endmodule
|
endmodule
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue