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
|
||||
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
|
||||
if newAddr >= 0 && newAddr < m.mem.Len() {
|
||||
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 false
|
||||
|
@ -165,7 +176,7 @@ func (m HexView) updateViewMode(msg tea.Msg) (HexView, tea.Cmd) {
|
|||
return m, nil
|
||||
}
|
||||
|
||||
func (m HexView) View(height int) string {
|
||||
func (m HexView) View() string {
|
||||
startAddr, endAddr := m.VisibleBytes()
|
||||
bytes := m.mem.Slice(startAddr, endAddr)
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ func main() {
|
|||
}
|
||||
defer dbg.Close()
|
||||
|
||||
mem := memory.New(dbg, 768)
|
||||
mem := memory.New(dbg, 4*1024)
|
||||
|
||||
if err := UI(mem); err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
|
@ -1,15 +1,23 @@
|
|||
package serial
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.bug.st/serial"
|
||||
)
|
||||
|
||||
const portDev = "/dev/ttyUSB0"
|
||||
const maxAddr = 1 << 17
|
||||
const (
|
||||
portDev = "/dev/ttyUSB0"
|
||||
portSpeed = 115_200
|
||||
maxAddr = 1 << 17
|
||||
pingResponse = 0xEA
|
||||
readTimeout = 2 * time.Second
|
||||
)
|
||||
|
||||
type Serial struct {
|
||||
portMu sync.Mutex
|
||||
|
@ -25,6 +33,21 @@ func Open() (*Serial, error) {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -34,42 +57,71 @@ func (d *Serial) Close() error {
|
|||
return d.port.Close()
|
||||
}
|
||||
|
||||
func encode(addr int, write bool, data byte) [4]byte {
|
||||
writeVal := uint8(0)
|
||||
if write {
|
||||
writeVal = 1
|
||||
}
|
||||
const (
|
||||
cmdReadByte = 1
|
||||
cmdWriteByte = 2
|
||||
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
|
||||
ret[3] = data
|
||||
ret[2] = byte(addr<<1) | writeVal
|
||||
ret[1] = byte(addr >> 7)
|
||||
ret[0] = byte(addr >> 15)
|
||||
binary.BigEndian.PutUint32(ret[:], raw)
|
||||
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()
|
||||
defer d.portMu.Unlock()
|
||||
if off+int64(len(bs)) >= maxAddr {
|
||||
return 0, errors.New("OOB read")
|
||||
}
|
||||
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 {
|
||||
return i, err
|
||||
}
|
||||
d.port.SetReadTimeout(2 * time.Second)
|
||||
n, err := d.port.Read(packet[:1])
|
||||
d.port.SetReadTimeout(readTimeout)
|
||||
n, err := io.ReadFull(d.port, bs[i:i+1])
|
||||
if err != nil {
|
||||
return i, err
|
||||
} else if n == 0 {
|
||||
return i, errors.New("short read")
|
||||
return i + n, err
|
||||
}
|
||||
bs[i] = packet[0]
|
||||
}
|
||||
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) {
|
||||
d.portMu.Lock()
|
||||
defer d.portMu.Unlock()
|
||||
|
@ -77,7 +129,7 @@ func (d *Serial) WriteAt(bs []byte, off int64) (int, error) {
|
|||
return 0, errors.New("OOB write")
|
||||
}
|
||||
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 {
|
||||
return i, err
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"git.sentinel65x.com/dave/gary/debugger/memory"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
|
@ -14,7 +13,7 @@ func UI(mem *memory.Memory) error {
|
|||
width: 0,
|
||||
height: 0,
|
||||
mem: mem,
|
||||
bottomMsg: "Loading...",
|
||||
statusMsg: "Loading...",
|
||||
}
|
||||
initial.hex = NewHexView(mem, initial.commitWrites)
|
||||
initial.hex.AddrStyle = text
|
||||
|
@ -31,11 +30,19 @@ func UI(mem *memory.Memory) error {
|
|||
var (
|
||||
amber = lip.Color("#ffb00")
|
||||
slate = lip.Color("235")
|
||||
text = lip.NewStyle().Foreground(amber).Background(slate)
|
||||
faintText = text.Faint(true)
|
||||
box = text.Border(lip.NormalBorder(), true, true, true, true).
|
||||
text = lip.NewStyle().
|
||||
Foreground(amber).
|
||||
Background(slate).
|
||||
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 {
|
||||
|
@ -56,27 +63,34 @@ type debugger struct {
|
|||
mem *memory.Memory
|
||||
hex HexView
|
||||
lastErr error
|
||||
bottomMsg string
|
||||
statusMsg string
|
||||
}
|
||||
|
||||
func (m debugger) Init() tea.Cmd {
|
||||
return tea.Sequence(
|
||||
m.readFullMemory(),
|
||||
staticMsg(msgUpdateStatus{"Initial load complete."}),
|
||||
)
|
||||
loads := m.readFullMemoryCmds()
|
||||
ret := make([]tea.Cmd, 0, (len(loads)*2)+1)
|
||||
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 {
|
||||
return tea.Sequence(m.readFullMemoryCmds()...)
|
||||
}
|
||||
|
||||
func (m debugger) readFullMemoryCmds() []tea.Cmd {
|
||||
var ret []tea.Cmd
|
||||
for i := 0; i < m.mem.Len(); i += 128 {
|
||||
ret = append(ret, m.readMemory(i, 128))
|
||||
}
|
||||
return tea.Sequence(ret...)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (m debugger) readMemory(start, count int) tea.Cmd {
|
||||
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 msgMemoryChanged{start, count}
|
||||
|
@ -103,13 +117,12 @@ func (m debugger) commitWrites() tea.Cmd {
|
|||
}
|
||||
|
||||
func (m debugger) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
log.Printf("msv: %#v", msg)
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
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
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
|
@ -118,13 +131,14 @@ func (m debugger) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
case "r":
|
||||
start, end := m.hex.VisibleBytes()
|
||||
return m, m.readMemory(start, end-start)
|
||||
case "R":
|
||||
return m, m.readFullMemory()
|
||||
}
|
||||
case msgErr:
|
||||
log.Print(msg.err)
|
||||
m.lastErr = msg.err
|
||||
return m, nil
|
||||
case msgUpdateStatus:
|
||||
m.bottomMsg = msg.status
|
||||
m.statusMsg = msg.status
|
||||
}
|
||||
var cmd tea.Cmd
|
||||
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")
|
||||
}
|
||||
|
||||
statusBar := text.Width(m.width).Height(1).Align(lip.Center)
|
||||
topSegment := statusBar.Width(m.width / 3).Reverse(true)
|
||||
hex := box.Render(m.hex.View())
|
||||
|
||||
hexBox := lip.NewStyle().Border(lip.NormalBorder()).BorderForeground(amber).BorderBackground(slate).Padding(1, 2)
|
||||
hexHeight := m.height - 2 - hexBox.GetVerticalFrameSize()
|
||||
hex := hexBox.Render(m.hex.View(hexHeight))
|
||||
barText := text.Reverse(true) //.Height(1)
|
||||
|
||||
topLeft := text.Reverse(true).Bold(true).Render("Addr: ") + text.Reverse(true).Render(fmt.Sprintf("0x%-5x", m.hex.SelectedAddr()))
|
||||
topLeft = topSegment.Padding(0, 1).Align(lip.Left).Render(topLeft)
|
||||
topMid := topSegment.Bold(true).Render("GARY Debugger")
|
||||
topRight := topSegment.Render("")
|
||||
topStatus := lip.JoinHorizontal(lip.Top, topLeft, topMid, topRight)
|
||||
bottomStatus := statusBar.Render(m.bottomMsg)
|
||||
topLeft := barText.Bold(true).PaddingLeft(1).Render("Addr: ")
|
||||
topLeft += barText.Render(fmt.Sprintf("0x%x (%d)", m.hex.SelectedAddr(), m.hex.SelectedAddr()))
|
||||
topRight := barText.PaddingRight(1).Render(m.statusMsg)
|
||||
title := barText.Padding(0, 1).Bold(true).Render("GARY Debugger")
|
||||
|
||||
top := lip.JoinVertical(lip.Center, topStatus, hex, bottomStatus)
|
||||
return top
|
||||
sideWidth := max(lip.Width(topLeft), lip.Width(topRight))
|
||||
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 UART::*;
|
||||
import VRAM::*;
|
||||
import Debugger::*;
|
||||
|
||||
module mkUARTDebugger(Integer clock_frequency, Integer uart_bitrate, VRAMServer mem, UART_PHY ifc);
|
||||
UART uart <- mkUART(clock_frequency, uart_bitrate);
|
||||
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#(VRAMResponse, Bit#(8)) encode <- mkPacker();
|
||||
Server#(Bit#(8), DebugRequest) decode <- mkUnpacker();
|
||||
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);
|
||||
mkConnection(decode.response, mem.request);
|
||||
mkConnection(mem.response, encode.request);
|
||||
mkConnection(encode.response, uart.send);
|
||||
let debug <- mkDebugger();
|
||||
mkConnection(debug_client, debug.server);
|
||||
mkConnection(debug.vram, mem);
|
||||
|
||||
return uart.phy;
|
||||
endmodule
|
||||
|
@ -38,7 +42,7 @@ endinterface
|
|||
module mkTop(Top);
|
||||
////////////
|
||||
// Memory
|
||||
VRAM mem <- mkVRAM(4);
|
||||
VRAM mem <- mkVRAM(128);
|
||||
|
||||
////////////
|
||||
// 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 BRAMCore::*;
|
||||
import Real::*;
|
||||
import FIFOF::*;
|
||||
import SpecialFIFOs::*;
|
||||
|
||||
import DelayLine::*;
|
||||
import ECP5_RAM::*;
|
||||
|
@ -21,9 +23,15 @@ typedef UInt#(17) VRAMAddr;
|
|||
typedef UInt#(2) ArrayAddr;
|
||||
typedef UInt#(3) ChipAddr;
|
||||
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 EBR#(ByteAddr, VRAMData, ByteAddr, VRAMData) ByteRAM;
|
||||
typedef struct {
|
||||
EBRAddr addr;
|
||||
Maybe#(data) data;
|
||||
} VRAMInternalRequest#(type data) deriving (Bits, Eq, FShow);
|
||||
|
||||
typedef struct {
|
||||
VRAMAddr addr;
|
||||
|
@ -34,6 +42,11 @@ typedef struct {
|
|||
VRAMData data;
|
||||
} 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);
|
||||
EBRPortConfig cfg = defaultValue;
|
||||
cfg.chip_select_addr = chip_addr;
|
||||
|
@ -61,96 +74,159 @@ module mkNibbleRAM_Sim(ChipAddr chip_addr, EBR#(ByteAddr, Bit#(4), ByteAddr, Bit
|
|||
endinterface
|
||||
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;
|
||||
if (genC())
|
||||
_ret <- mkNibbleRAM_Sim(chip_addr);
|
||||
else
|
||||
_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
|
||||
|
||||
// mkByteRAM glues two ECP5 EBRs together to make a 4096x8b memory
|
||||
// block. Like the underlying ECP5 EBRs, callers must bring their own
|
||||
// flow control to read out responses one cycle after putting a read
|
||||
// request.
|
||||
module mkByteRAM(ChipAddr chip_addr, ByteRAM ifc);
|
||||
EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) upper <- mkNibbleRAM(chip_addr);
|
||||
EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) lower <- mkNibbleRAM(chip_addr);
|
||||
module mkByteRAM(ChipAddr chip_addr, VRAMCoreInternal#(VRAMData) ifc);
|
||||
VRAMCoreInternal#(Bit#(4)) upper <- mkNibbleRAM(chip_addr);
|
||||
VRAMCoreInternal#(Bit#(4)) lower <- mkNibbleRAM(chip_addr);
|
||||
|
||||
interface EBRPort portA;
|
||||
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
|
||||
upper.portA.put(chip_select, write, addr, truncate(data_in>>4));
|
||||
lower.portA.put(chip_select, write, addr, truncate(data_in));
|
||||
endmethod
|
||||
|
||||
method VRAMData read();
|
||||
return (extend(upper.portA.read())<<4) | (extend(lower.portA.read()));
|
||||
interface Server portA;
|
||||
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.portA.request.put(VRAMInternalRequest{
|
||||
addr: req.addr,
|
||||
data: ud
|
||||
});
|
||||
lower.portA.request.put(VRAMInternalRequest{
|
||||
addr: req.addr,
|
||||
data: ld
|
||||
});
|
||||
endmethod
|
||||
endinterface
|
||||
|
||||
interface EBRPort portB;
|
||||
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
|
||||
upper.portB.put(chip_select, write, addr, truncate(data_in>>4));
|
||||
lower.portB.put(chip_select, write, addr, truncate(data_in));
|
||||
interface Get response;
|
||||
method ActionValue#(VRAMData) get();
|
||||
let u <- upper.portA.response.get();
|
||||
let l <- lower.portA.response.get();
|
||||
return {u, l};
|
||||
endmethod
|
||||
endinterface
|
||||
endinterface
|
||||
|
||||
method VRAMData read();
|
||||
return (extend(upper.portB.read())<<4) | (extend(lower.portB.read()));
|
||||
interface Server portB;
|
||||
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
|
||||
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
|
||||
|
||||
// mkByteRAMArray arrays up to 8 mkByteRAMs together, using the
|
||||
// hardwired chip select lines to route inputs appropriately and a mux
|
||||
// tree to collect outputs. With num_chips=8, the resulting ByteRAM is
|
||||
// 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)
|
||||
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)
|
||||
blocks[i] <- mkByteRAM(fromInteger(i));
|
||||
|
||||
DelayLine#(ChipAddr) read_chip_A <- mkDelayLine(1);
|
||||
DelayLine#(ChipAddr) read_chip_B <- mkDelayLine(1);
|
||||
FIFOF#(ChipAddr) read_chip_A <- mkPipelineFIFOF();
|
||||
FIFOF#(ChipAddr) read_chip_B <- mkPipelineFIFOF();
|
||||
|
||||
interface EBRPort portA;
|
||||
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
|
||||
interface Server portA;
|
||||
interface Put request;
|
||||
method Action put(req) if (read_chip_A.notFull);
|
||||
for (Integer i=0; i<num_chips; i=i+1)
|
||||
blocks[i].portA.put(chip_select, write, addr, data_in);
|
||||
if (!write)
|
||||
read_chip_A <= chip_select;
|
||||
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;
|
||||
blocks[i].portA.request.put(req);
|
||||
if (!isValid(req.data))
|
||||
read_chip_A.enq(req.addr.chip);
|
||||
endmethod
|
||||
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;
|
||||
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
|
||||
interface Server portB;
|
||||
interface Put request;
|
||||
method Action put(req) if (read_chip_B.notFull);
|
||||
for (Integer i=0; i<num_chips; i=i+1)
|
||||
blocks[i].portB.put(chip_select, write, addr, data_in);
|
||||
if (!write)
|
||||
read_chip_B <= chip_select;
|
||||
blocks[i].portB.request.put(req);
|
||||
if (!isValid(req.data))
|
||||
read_chip_B.enq(req.addr.chip);
|
||||
endmethod
|
||||
method VRAMData read();
|
||||
if (read_chip_B.ready)
|
||||
if (read_chip_B <= fromInteger(num_chips-1))
|
||||
return blocks[read_chip_B].portB.read();
|
||||
else
|
||||
return 0;
|
||||
else
|
||||
return 0;
|
||||
endinterface
|
||||
interface Get response;
|
||||
method ActionValue#(VRAMData) get();
|
||||
read_chip_B.deq();
|
||||
let res <- blocks[read_chip_B.first].portB.response.get();
|
||||
return res;
|
||||
endmethod
|
||||
endinterface
|
||||
endinterface
|
||||
endmodule
|
||||
|
||||
interface VRAMCore;
|
||||
|
@ -180,49 +256,57 @@ module mkVRAMCore(Integer num_kilobytes, VRAMCore ifc);
|
|||
let num_byterams = num_bytes/4096;
|
||||
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));
|
||||
endfunction
|
||||
|
||||
ByteRAM arrays[num_arrays];
|
||||
VRAMCoreInternal#(VRAMData) arrays[num_arrays];
|
||||
for (Integer i=0; i<num_arrays; i=i+1) begin
|
||||
let array_size = min(num_byterams - (i*8), 8);
|
||||
arrays[i] <- mkByteRAMArray(array_size);
|
||||
end
|
||||
|
||||
Reg#(Maybe#(ArrayAddr)) inflight_A[2] <- mkCReg(2, tagged Invalid);
|
||||
Reg#(Maybe#(ArrayAddr)) inflight_B[2] <- mkCReg(2, tagged Invalid);
|
||||
FIFOF#(ArrayAddr) array_addr_A <- mkPipelineFIFOF();
|
||||
FIFOF#(ArrayAddr) array_addr_B <- mkPipelineFIFOF();
|
||||
|
||||
interface Server portA;
|
||||
interface Put request;
|
||||
method Action put(VRAMRequest req) if (inflight_A[1] matches tagged Invalid);
|
||||
match {.array, .chip, .byteaddr} = split_addr(req.addr);
|
||||
arrays[array].portA.put(chip, isValid(req.data), byteaddr, fromMaybe(0, req.data));
|
||||
method Action put(req);
|
||||
match {.array, .addr} = split_addr(req.addr);
|
||||
arrays[array].portA.request.put(VRAMInternalRequest{
|
||||
addr: addr,
|
||||
data: req.data
|
||||
});
|
||||
if (!isValid(req.data))
|
||||
inflight_A[1] <= tagged Valid array;
|
||||
array_addr_A.enq(array);
|
||||
endmethod
|
||||
endinterface
|
||||
interface Get response;
|
||||
method ActionValue#(VRAMResponse) get() if (inflight_A[0] matches tagged Valid .array);
|
||||
inflight_A[0] <= tagged Invalid;
|
||||
return VRAMResponse{data: arrays[array].portA.read()};
|
||||
method ActionValue#(VRAMResponse) get();
|
||||
array_addr_A.deq();
|
||||
let ret <- arrays[array_addr_A.first].portA.response.get();
|
||||
return VRAMResponse{data: ret};
|
||||
endmethod
|
||||
endinterface
|
||||
endinterface
|
||||
|
||||
interface Server portB;
|
||||
interface Put request;
|
||||
method Action put(VRAMRequest req) if (inflight_B[1] matches tagged Invalid);
|
||||
match {.array, .chip, .byteaddr} = split_addr(req.addr);
|
||||
arrays[array].portB.put(0, isValid(req.data), byteaddr, fromMaybe(0, req.data));
|
||||
method Action put(req);
|
||||
match {.array, .addr} = split_addr(req.addr);
|
||||
arrays[array].portB.request.put(VRAMInternalRequest{
|
||||
addr: addr,
|
||||
data: req.data
|
||||
});
|
||||
if (!isValid(req.data))
|
||||
inflight_B[1] <= tagged Valid array;
|
||||
array_addr_B.enq(array);
|
||||
endmethod
|
||||
endinterface
|
||||
interface Get response;
|
||||
method ActionValue#(VRAMResponse) get() if (inflight_B[0] matches tagged Valid .array);
|
||||
inflight_B[0] <= tagged Invalid;
|
||||
return VRAMResponse{data: arrays[array].portB.read()};
|
||||
method ActionValue#(VRAMResponse) get();
|
||||
array_addr_B.deq();
|
||||
let ret <- arrays[array_addr_B.first].portB.response.get();
|
||||
return VRAMResponse{data: ret};
|
||||
endmethod
|
||||
endinterface
|
||||
endinterface
|
||||
|
|
|
@ -21,40 +21,46 @@ function ActionValue#(Bool) verbose();
|
|||
endactionvalue);
|
||||
endfunction
|
||||
|
||||
typedef (function ActionValue#(Bit#(8)) next()) ValFn;
|
||||
|
||||
function ValFn constant_value(Integer cnst);
|
||||
function ActionValue#(Bit#(8)) next();
|
||||
return (actionvalue
|
||||
module mkConstantValue(Integer cnst, Get#(Bit#(8)) ifc);
|
||||
method ActionValue#(Bit#(8)) get();
|
||||
return fromInteger(cnst);
|
||||
endactionvalue);
|
||||
endfunction
|
||||
endmethod
|
||||
endmodule
|
||||
|
||||
return next;
|
||||
endfunction
|
||||
|
||||
module mkIncrementingValue(ValFn);
|
||||
module mkIncrementingValue(Get#(Bit#(8)));
|
||||
Reg#(Bit#(8)) val <- mkReg(0);
|
||||
|
||||
function ActionValue#(Bit#(8)) next();
|
||||
return (actionvalue
|
||||
// Cycle through 101 values. 101 is prime, so the
|
||||
// pattern it generates doesn't align to a power of
|
||||
// two and should detect any memory mapping errors.
|
||||
method ActionValue#(Bit#(8)) get();
|
||||
// Cycle through 101 values. 101 is prime, so the pattern it
|
||||
// generates doesn't align to a power of two and should detect
|
||||
// any memory mapping errors.
|
||||
if (val == 100)
|
||||
val <= 0;
|
||||
else
|
||||
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
|
||||
|
||||
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 cycles <- 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");
|
||||
write_cycle_time.reset();
|
||||
|
||||
let data <- next_value();
|
||||
let data <- next_value.get();
|
||||
let req = VRAMRequest{
|
||||
addr: idx,
|
||||
data: tagged Valid data
|
||||
|
@ -96,7 +102,7 @@ module mkWriter(Server#(VRAMRequest, VRAMResponse) dut, ValFn next_value, Machin
|
|||
endmethod
|
||||
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 cycles <- mkCycleCounter();
|
||||
|
||||
|
@ -126,11 +132,11 @@ module mkReader(Server#(VRAMRequest, VRAMResponse) dut, ValFn next_value, Machin
|
|||
|
||||
rule verify_read (verify_remaining > 0);
|
||||
let got <- dut.response.get();
|
||||
let want <- next_value();
|
||||
dynamicAssert(got.data == want, "wrong value seen during read");
|
||||
let want <- next_value.get();
|
||||
|
||||
if (flags.verbose)
|
||||
$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)
|
||||
$display("Verified %0d reads in %0d cycles", total, cycles);
|
||||
|
@ -187,17 +193,36 @@ module mkTwoPortTest(VRAMCore dut, Stmt ret);
|
|||
endseq);
|
||||
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" *)
|
||||
module mkTB(Empty);
|
||||
let dut <- mkVRAMCore(112);
|
||||
|
||||
let simple <- mkSimpleTest(dut);
|
||||
let two_port <- mkTwoPortTest(dut);
|
||||
let slow_reader <- mkSlowConsumerTest(dut);
|
||||
|
||||
runTest(100000,
|
||||
mkTest("VRAMCore", seq
|
||||
mkTest("VRAMCore/simple", simple);
|
||||
mkTest("VRAMCore/two_port", two_port);
|
||||
//mkTest("VRAMCore/simple", simple);
|
||||
//mkTest("VRAMCore/two_port", two_port);
|
||||
mkTest("VRAMCore/slow_reader", slow_reader);
|
||||
endseq));
|
||||
endmodule
|
||||
|
||||
|
|
Loading…
Reference in New Issue