Compare commits

..

No commits in common. "2156d824c51ec02e92782ef1e681a0bc52ee5918" and "e2f4103fdce7912a0510830cec51e306eb26a338" have entirely different histories.

11 changed files with 184 additions and 679 deletions

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
.envrc .envrc
out/ out/
log.txt

View File

@ -1,180 +0,0 @@
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

View File

@ -1,132 +0,0 @@
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

View File

@ -56,17 +56,6 @@ 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
@ -176,7 +165,7 @@ func (m HexView) updateViewMode(msg tea.Msg) (HexView, tea.Cmd) {
return m, nil return m, nil
} }
func (m HexView) View() string { func (m HexView) View(height int) string {
startAddr, endAddr := m.VisibleBytes() startAddr, endAddr := m.VisibleBytes()
bytes := m.mem.Slice(startAddr, endAddr) bytes := m.mem.Slice(startAddr, endAddr)

View File

@ -14,7 +14,7 @@ func main() {
} }
defer dbg.Close() defer dbg.Close()
mem := memory.New(dbg, 4*1024) mem := memory.New(dbg, 768)
if err := UI(mem); err != nil { if err := UI(mem); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -1,23 +1,15 @@
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 ( const portDev = "/dev/ttyUSB0"
portDev = "/dev/ttyUSB0" const maxAddr = 1 << 17
portSpeed = 115_200
maxAddr = 1 << 17
pingResponse = 0xEA
readTimeout = 2 * time.Second
)
type Serial struct { type Serial struct {
portMu sync.Mutex portMu sync.Mutex
@ -33,21 +25,6 @@ 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
} }
@ -57,71 +34,42 @@ func (d *Serial) Close() error {
return d.port.Close() return d.port.Close()
} }
const ( func encode(addr int, write bool, data byte) [4]byte {
cmdReadByte = 1 writeVal := uint8(0)
cmdWriteByte = 2 if write {
cmdReadRange = 3 writeVal = 1
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
binary.BigEndian.PutUint32(ret[:], raw) ret[3] = data
ret[2] = byte(addr<<1) | writeVal
ret[1] = byte(addr >> 7)
ret[0] = byte(addr >> 15)
return ret return ret
} }
// readSlow reads memory one byte at a time, and is 10x slower than func (d *Serial) ReadAt(bs []byte, off int64) (int, error) {
// 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(cmdReadByte, int(off)+i, 0) packet := encode(int(off)+i, false, 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(readTimeout) d.port.SetReadTimeout(2 * time.Second)
n, err := io.ReadFull(d.port, bs[i:i+1]) n, err := d.port.Read(packet[:1])
if err != nil { if err != nil {
return i + n, err
}
}
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 return i, err
} else if n == 0 {
return i, errors.New("short read")
} }
d.port.SetReadTimeout(readTimeout) bs[i] = packet[0]
n, err := io.ReadFull(d.port, bs[i:i+burstLen])
if err != nil {
return i + n, err
}
} }
return len(bs), nil 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()
@ -129,7 +77,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(cmdWriteByte, int(off)+i, v) packet := encode(int(off)+i, true, v)
if _, err := d.port.Write(packet[:]); err != nil { if _, err := d.port.Write(packet[:]); err != nil {
return i, err return i, err
} }

View File

@ -2,6 +2,7 @@ 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"
@ -13,7 +14,7 @@ func UI(mem *memory.Memory) error {
width: 0, width: 0,
height: 0, height: 0,
mem: mem, mem: mem,
statusMsg: "Loading...", bottomMsg: "Loading...",
} }
initial.hex = NewHexView(mem, initial.commitWrites) initial.hex = NewHexView(mem, initial.commitWrites)
initial.hex.AddrStyle = text initial.hex.AddrStyle = text
@ -28,21 +29,13 @@ 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(). text = lip.NewStyle().Foreground(amber).Background(slate)
Foreground(amber).
Background(slate).
BorderForeground(amber).
BorderBackground(slate).
Border(lip.NormalBorder(), false, false, false, false)
faintText = text.Faint(true) faintText = text.Faint(true)
box = text.Border(lip.NormalBorder(), true, true, true, true).
box = lip.NewStyle(). BorderForeground(amber).
Border(lip.NormalBorder()). BorderBackground(slate)
BorderForeground(amber).
BorderBackground(slate).
Padding(1, 2)
) )
type msgMemoryChanged struct { type msgMemoryChanged struct {
@ -63,34 +56,27 @@ type debugger struct {
mem *memory.Memory mem *memory.Memory
hex HexView hex HexView
lastErr error lastErr error
statusMsg string bottomMsg string
} }
func (m debugger) Init() tea.Cmd { func (m debugger) Init() tea.Cmd {
loads := m.readFullMemoryCmds() return tea.Sequence(
ret := make([]tea.Cmd, 0, (len(loads)*2)+1) m.readFullMemory(),
for i, c := range loads { staticMsg(msgUpdateStatus{"Initial load complete."}),
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 ret return tea.Sequence(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.Refresh(start, count); err != nil { if err := m.mem.Load(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}
@ -117,12 +103,13 @@ 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 - box.GetVerticalFrameSize() - 1}) m.hex, cmd = m.hex.Update(HexViewSetHeight{m.height - 6}) // TODO: make less shit
return m, cmd return m, cmd
case tea.KeyMsg: case tea.KeyMsg:
switch msg.String() { switch msg.String() {
@ -131,14 +118,13 @@ 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.statusMsg = msg.status m.bottomMsg = msg.status
} }
var cmd tea.Cmd var cmd tea.Cmd
m.hex, cmd = m.hex.Update(msg) m.hex, cmd = m.hex.Update(msg)
@ -172,23 +158,20 @@ 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")
} }
hex := box.Render(m.hex.View()) statusBar := text.Width(m.width).Height(1).Align(lip.Center)
topSegment := statusBar.Width(m.width / 3).Reverse(true)
barText := text.Reverse(true) //.Height(1) 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))
topLeft := barText.Bold(true).PaddingLeft(1).Render("Addr: ") topLeft := text.Reverse(true).Bold(true).Render("Addr: ") + text.Reverse(true).Render(fmt.Sprintf("0x%-5x", m.hex.SelectedAddr()))
topLeft += barText.Render(fmt.Sprintf("0x%x (%d)", m.hex.SelectedAddr(), m.hex.SelectedAddr())) topLeft = topSegment.Padding(0, 1).Align(lip.Left).Render(topLeft)
topRight := barText.PaddingRight(1).Render(m.statusMsg) topMid := topSegment.Bold(true).Render("GARY Debugger")
title := barText.Padding(0, 1).Bold(true).Render("GARY Debugger") topRight := topSegment.Render("")
topStatus := lip.JoinHorizontal(lip.Top, topLeft, topMid, topRight)
bottomStatus := statusBar.Render(m.bottomMsg)
sideWidth := max(lip.Width(topLeft), lip.Width(topRight)) top := lip.JoinVertical(lip.Center, topStatus, hex, bottomStatus)
titleWidth := m.width - 2*sideWidth return top
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
} }

View File

@ -8,22 +8,18 @@ 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), DebugRequest) decode <- mkUnpacker(); Server#(Bit#(8), VRAMRequest) decode <- mkUnpacker();
Server#(DebugResponse, Bit#(8)) encode <- mkPacker(); Server#(VRAMResponse, 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);
let debug <- mkDebugger(); mkConnection(uart.receive, decode.request);
mkConnection(debug_client, debug.server); mkConnection(decode.response, mem.request);
mkConnection(debug.vram, mem); mkConnection(mem.response, encode.request);
mkConnection(encode.response, uart.send);
return uart.phy; return uart.phy;
endmodule endmodule
@ -42,7 +38,7 @@ endinterface
module mkTop(Top); module mkTop(Top);
//////////// ////////////
// Memory // Memory
VRAM mem <- mkVRAM(128); VRAM mem <- mkVRAM(4);
//////////// ////////////
// Debugging // Debugging

11
log.txt Normal file
View File

@ -0,0 +1,11 @@
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}

View File

@ -4,8 +4,6 @@ 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::*;
@ -23,15 +21,9 @@ 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);
typedef struct { // ByteRAM is two EBRs glued together to make a whole-byte memory.
EBRAddr addr; typedef EBR#(ByteAddr, VRAMData, ByteAddr, VRAMData) ByteRAM;
Maybe#(data) data;
} VRAMInternalRequest#(type data) deriving (Bits, Eq, FShow);
typedef struct { typedef struct {
VRAMAddr addr; VRAMAddr addr;
@ -42,11 +34,6 @@ 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;
@ -74,102 +61,43 @@ module mkNibbleRAM_Sim(ChipAddr chip_addr, EBR#(ByteAddr, Bit#(4), ByteAddr, Bit
endinterface endinterface
endmodule endmodule
module mkNibbleRAM(ChipAddr chip_addr, VRAMCoreInternal#(Bit#(4)) ifc); module mkNibbleRAM(ChipAddr chip_addr, EBR#(ByteAddr, Bit#(4), ByteAddr, 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, VRAMCoreInternal#(VRAMData) ifc); module mkByteRAM(ChipAddr chip_addr, ByteRAM ifc);
VRAMCoreInternal#(Bit#(4)) upper <- mkNibbleRAM(chip_addr); EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) upper <- mkNibbleRAM(chip_addr);
VRAMCoreInternal#(Bit#(4)) lower <- mkNibbleRAM(chip_addr); EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) lower <- mkNibbleRAM(chip_addr);
interface Server portA; interface EBRPort portA;
interface Put request; method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
method Action put(req); upper.portA.put(chip_select, write, addr, truncate(data_in>>4));
Maybe#(Bit#(4)) ud = tagged Invalid; lower.portA.put(chip_select, write, addr, truncate(data_in));
Maybe#(Bit#(4)) ld = tagged Invalid; endmethod
if (req.data matches tagged Valid .data) begin
ud = tagged Valid data[7:4]; method VRAMData read();
ld = tagged Valid data[3:0]; return (extend(upper.portA.read())<<4) | (extend(lower.portA.read()));
end endmethod
upper.portA.request.put(VRAMInternalRequest{
addr: req.addr,
data: ud
});
lower.portA.request.put(VRAMInternalRequest{
addr: req.addr,
data: ld
});
endmethod
endinterface
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 endinterface
interface Server portB; interface EBRPort portB;
interface Put request; method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
method Action put(req); upper.portB.put(chip_select, write, addr, truncate(data_in>>4));
Maybe#(Bit#(4)) ud = tagged Invalid; lower.portB.put(chip_select, write, addr, truncate(data_in));
Maybe#(Bit#(4)) ld = tagged Invalid; endmethod
if (req.data matches tagged Valid .data) begin
ud = tagged Valid data[7:4]; method VRAMData read();
ld = tagged Valid data[3:0]; return (extend(upper.portB.read())<<4) | (extend(lower.portB.read()));
end endmethod
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 endinterface
endmodule : mkByteRAM endmodule : mkByteRAM
@ -177,55 +105,51 @@ endmodule : mkByteRAM
// 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");
VRAMCoreInternal#(VRAMData) blocks[num_chips]; ByteRAM 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));
FIFOF#(ChipAddr) read_chip_A <- mkPipelineFIFOF(); DelayLine#(ChipAddr) read_chip_A <- mkDelayLine(1);
FIFOF#(ChipAddr) read_chip_B <- mkPipelineFIFOF(); DelayLine#(ChipAddr) read_chip_B <- mkDelayLine(1);
interface Server portA; interface EBRPort portA;
interface Put request; method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
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
endmethod method VRAMData read();
endinterface if (read_chip_A.ready)
interface Get response; if (read_chip_A <= fromInteger(num_chips-1))
method ActionValue#(VRAMData) get(); return blocks[read_chip_A].portA.read();
read_chip_A.deq(); else
let res <- blocks[read_chip_A.first].portA.response.get(); return 0;
return res; else
endmethod return 0;
endinterface endmethod
endinterface endinterface
interface Server portB; interface EBRPort portB;
interface Put request; method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
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
endmethod return 0;
endinterface endmethod
endinterface endinterface
endmodule endmodule
@ -256,57 +180,49 @@ 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 Tuple2#(ArrayAddr, EBRAddr) split_addr(VRAMAddr a); function Tuple3#(ArrayAddr, ChipAddr, ByteAddr) split_addr(VRAMAddr a);
return unpack(pack(a)); return unpack(pack(a));
endfunction endfunction
VRAMCoreInternal#(VRAMData) arrays[num_arrays]; ByteRAM 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
FIFOF#(ArrayAddr) array_addr_A <- mkPipelineFIFOF(); Reg#(Maybe#(ArrayAddr)) inflight_A[2] <- mkCReg(2, tagged Invalid);
FIFOF#(ArrayAddr) array_addr_B <- mkPipelineFIFOF(); Reg#(Maybe#(ArrayAddr)) inflight_B[2] <- mkCReg(2, tagged Invalid);
interface Server portA; interface Server portA;
interface Put request; interface Put request;
method Action put(req); method Action put(VRAMRequest req) if (inflight_A[1] matches tagged Invalid);
match {.array, .addr} = split_addr(req.addr); match {.array, .chip, .byteaddr} = split_addr(req.addr);
arrays[array].portA.request.put(VRAMInternalRequest{ arrays[array].portA.put(chip, isValid(req.data), byteaddr, fromMaybe(0, req.data));
addr: addr,
data: req.data
});
if (!isValid(req.data)) if (!isValid(req.data))
array_addr_A.enq(array); inflight_A[1] <= tagged Valid array;
endmethod endmethod
endinterface endinterface
interface Get response; interface Get response;
method ActionValue#(VRAMResponse) get(); method ActionValue#(VRAMResponse) get() if (inflight_A[0] matches tagged Valid .array);
array_addr_A.deq(); inflight_A[0] <= tagged Invalid;
let ret <- arrays[array_addr_A.first].portA.response.get(); return VRAMResponse{data: arrays[array].portA.read()};
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(req); method Action put(VRAMRequest req) if (inflight_B[1] matches tagged Invalid);
match {.array, .addr} = split_addr(req.addr); match {.array, .chip, .byteaddr} = split_addr(req.addr);
arrays[array].portB.request.put(VRAMInternalRequest{ arrays[array].portB.put(0, isValid(req.data), byteaddr, fromMaybe(0, req.data));
addr: addr,
data: req.data
});
if (!isValid(req.data)) if (!isValid(req.data))
array_addr_B.enq(array); inflight_B[1] <= tagged Valid array;
endmethod endmethod
endinterface endinterface
interface Get response; interface Get response;
method ActionValue#(VRAMResponse) get(); method ActionValue#(VRAMResponse) get() if (inflight_B[0] matches tagged Valid .array);
array_addr_B.deq(); inflight_B[0] <= tagged Invalid;
let ret <- arrays[array_addr_B.first].portB.response.get(); return VRAMResponse{data: arrays[array].portB.read()};
return VRAMResponse{data: ret};
endmethod endmethod
endinterface endinterface
endinterface endinterface

View File

@ -21,46 +21,40 @@ function ActionValue#(Bool) verbose();
endactionvalue); endactionvalue);
endfunction endfunction
module mkConstantValue(Integer cnst, Get#(Bit#(8)) ifc); typedef (function ActionValue#(Bit#(8)) next()) ValFn;
method ActionValue#(Bit#(8)) get();
return fromInteger(cnst);
endmethod
endmodule
module mkIncrementingValue(Get#(Bit#(8))); function ValFn constant_value(Integer cnst);
function ActionValue#(Bit#(8)) next();
return (actionvalue
return fromInteger(cnst);
endactionvalue);
endfunction
return next;
endfunction
module mkIncrementingValue(ValFn);
Reg#(Bit#(8)) val <- mkReg(0); Reg#(Bit#(8)) val <- mkReg(0);
method ActionValue#(Bit#(8)) get(); function ActionValue#(Bit#(8)) next();
// Cycle through 101 values. 101 is prime, so the pattern it return (actionvalue
// generates doesn't align to a power of two and should detect // Cycle through 101 values. 101 is prime, so the
// any memory mapping errors. // pattern it generates doesn't align to a power of
if (val == 100) // two and should detect any memory mapping errors.
val <= 0; if (val == 100)
else val <= 0;
val <= val+1; else
val <= val+1;
// Add another number to get all nonzero values, to
// detect writes that don't stick.
return 23+val;
endactionvalue);
endfunction
// Add another number to get all nonzero values, to detect return next;
// writes that don't stick.
return 23+val;
endmethod
endmodule endmodule
module mkSlowReader(Get#(Bit#(8)) inner, Get#(Bit#(8)) ifc); module mkWriter(Server#(VRAMRequest, VRAMResponse) dut, ValFn next_value, Machine 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();
@ -73,7 +67,7 @@ module mkWriter(Server#(VRAMRequest, VRAMResponse) dut, Get#(Bit#(8)) next_value
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.get(); let data <- next_value();
let req = VRAMRequest{ let req = VRAMRequest{
addr: idx, addr: idx,
data: tagged Valid data data: tagged Valid data
@ -102,7 +96,7 @@ module mkWriter(Server#(VRAMRequest, VRAMResponse) dut, Get#(Bit#(8)) next_value
endmethod endmethod
endmodule endmodule
module mkReader(Server#(VRAMRequest, VRAMResponse) dut, Get#(Bit#(8)) next_value, Machine ifc); module mkReader(Server#(VRAMRequest, VRAMResponse) dut, ValFn next_value, Machine ifc);
let flags <- mkTestFlags(); let flags <- mkTestFlags();
let cycles <- mkCycleCounter(); let cycles <- mkCycleCounter();
@ -132,11 +126,11 @@ module mkReader(Server#(VRAMRequest, VRAMResponse) dut, Get#(Bit#(8)) next_value
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.get(); let want <- next_value();
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);
@ -193,36 +187,17 @@ 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