Compare commits

..

7 Commits

Author SHA1 Message Date
David Anderson d3ab2fa433 debugger: start of a debugger client that uses the serial debug gateware 2024-09-14 20:32:40 -07:00
David Anderson 8a525d99a0 hardware/ulx3s: wire up blinky, tidy up the debugger a bit
IT WORKS!
2024-09-14 20:31:58 -07:00
David Anderson f7e3f36254 vram/VRAM: add tests for the arbitration glue and the entire VRAM stack 2024-09-14 20:26:55 -07:00
David Anderson 8247661a38 vram/VRAMCore: derive FShow for request and response types
Handy in unit tests to display the structs as they fly around.
2024-09-14 20:25:30 -07:00
David Anderson 65d13a0e50 vram/VRAM: only expect a response on reads
The VRAMCore doesn't generate responses for writes, so demanding one here
deadlocks the port the first time it writes something.
2024-09-14 20:24:40 -07:00
David Anderson e021e7d356 blinky/Blinky: a module that blinks a LED every second
Handy as a basic liveness test when you push stuff to an FPGA and
nothing happens.
2024-09-14 20:23:39 -07:00
David Anderson 227526c2b1 lib/ECP5_RAM: invert reset signal going into the primitive
Bluespec uses active-low reset signals, whereas the ECP5 primitives
use active-high. So this was holding the EBRs in reset after the rest
of the design was running. Oops.
2024-09-14 20:16:44 -07:00
10 changed files with 316 additions and 17 deletions

26
blinky/Blinky.bsv Normal file
View File

@ -0,0 +1,26 @@
package Blinky;
import Strobe::*;
(* always_ready *)
interface Blinky;
method Bool led_on();
endinterface
// mkBlinky returns a module that toggles its output approximately
// once a second. It's intended to be wired to an LED as a basic "are
// you alive" indicator.
module mkBlinky(Integer clock_frequency, Blinky ifc);
let strobe <- mkStrobe(clock_frequency, 1);
Reg#(Bool) out <- mkReg(False);
(* no_implicit_conditions,fire_when_enabled *)
rule increment (strobe);
out <= !out;
endrule
method led_on = out._read;
endmodule
endpackage

105
debugger/main.go Normal file
View File

@ -0,0 +1,105 @@
package main
import (
"errors"
"fmt"
"log"
"sync"
"time"
"go.bug.st/serial"
)
const portDev = "/dev/ttyUSB0"
func main() {
dbg, err := Open()
if err != nil {
log.Fatalf("connecting to debugger: %v", err)
}
defer dbg.Close()
fmt.Println("Writing...")
if err := dbg.Write(0x42, 123); err != nil {
log.Fatalf("writing to memory: %v", err)
}
v, err := dbg.Read(0x42)
if err != nil {
log.Fatalf("reading from memory: %v", err)
}
fmt.Printf("addr 0: %02x\n", v)
}
type Debugger struct {
port serial.Port
mu sync.Mutex
}
func Open() (*Debugger, error) {
mode := &serial.Mode{
BaudRate: 115_200,
}
port, err := serial.Open(portDev, mode)
if err != nil {
return nil, err
}
return &Debugger{port: port}, nil
}
func (d *Debugger) Close() error {
d.mu.Lock() // note, deliberately no unlocking, to poison.
return d.port.Close()
}
func encode(addr int, write bool, data byte) [4]byte {
writeVal := uint8(0)
if write {
writeVal = 1
}
var ret [4]byte
ret[3] = data
ret[2] = byte(addr<<1) | writeVal
ret[1] = byte(addr >> 7)
ret[0] = byte(addr >> 15)
return ret
}
func (d *Debugger) Read(addr int) (byte, error) {
d.mu.Lock()
defer d.mu.Unlock()
if addr >= 2<<17 {
return 0, fmt.Errorf("read %d out of bounds", addr)
}
packet := encode(addr, false, 0)
fmt.Printf("Writing: %02x %02x %02x %02x\n", packet[0], packet[1], packet[2], packet[3])
if _, err := d.port.Write(packet[:]); err != nil {
return 0, err
}
d.port.SetReadTimeout(2 * time.Second)
n, err := d.port.Read(packet[:1])
if err != nil {
return 0, err
} else if n == 0 {
return 0, errors.New("no read")
}
return packet[0], nil
}
func (d *Debugger) Write(addr int, val byte) error {
d.mu.Lock()
defer d.mu.Unlock()
if addr >= 2<<17 {
return fmt.Errorf("write %d out of bounds", addr)
}
packet := encode(addr, true, val)
fmt.Printf("Writing: %02x %02x %02x %02x\n", packet[0], packet[1], packet[2], packet[3])
if _, err := d.port.Write(packet[:]); err != nil {
return err
}
return nil
}

9
go.mod Normal file
View File

@ -0,0 +1,9 @@
module git.sentinel65x.com/dave/gary
go 1.22.6
require (
github.com/creack/goselect v0.1.2 // indirect
go.bug.st/serial v1.6.2 // indirect
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect
)

6
go.sum Normal file
View File

@ -0,0 +1,6 @@
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8=
go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -4,23 +4,24 @@ import Connectable::*;
import GetPut::*; import GetPut::*;
import ClientServer::*; import ClientServer::*;
import Blinky::*;
import PackUnpack::*; import PackUnpack::*;
import UART::*; import UART::*;
import VRAM::*; import VRAM::*;
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
Server#(Bit#(8), VRAMRequest) _decode <- mkUnpacker(); Server#(Bit#(8), VRAMRequest) decode <- mkUnpacker();
Server#(VRAMResponse, Bit#(8)) _encode <- mkPacker(); Server#(VRAMResponse, Bit#(8)) encode <- mkPacker();
mkConnection(_uart.receive, _decode.request); mkConnection(uart.receive, decode.request);
mkConnection(_decode.response, mem.request); mkConnection(decode.response, mem.request);
mkConnection(mem.response, _encode.request); mkConnection(mem.response, encode.request);
mkConnection(_encode.response, _uart.send); mkConnection(encode.response, uart.send);
return _uart.phy; return uart.phy;
endmodule endmodule
interface Top; interface Top;
@ -28,20 +29,28 @@ interface Top;
method Action debugger_rx_in((* port="serial_in" *) bit b); method Action debugger_rx_in((* port="serial_in" *) bit b);
(* always_ready,result="debug_serial_out" *) (* always_ready,result="debug_serial_out" *)
method bit debugger_tx_out(); method bit debugger_tx_out();
(* always_ready *)
method Bool led();
endinterface endinterface
(* synthesize *) (* synthesize *)
module mkTop(Top); module mkTop(Top);
//////////// ////////////
// Memory // Memory
VRAM mem <- mkVRAM(128); VRAM mem <- mkVRAM(4);
//////////// ////////////
// Debug interface // Debugging
let debugger <- mkUARTDebugger(25_000_000, 115_200, mem.debugger); let debugger <- mkUARTDebugger(25_000_000, 115_200, mem.debugger);
let blinky <- mkBlinky(25_000_000);
////////////
// External interface
method debugger_rx_in = debugger.rx_in; method debugger_rx_in = debugger.rx_in;
method debugger_tx_out = debugger.tx_out; method debugger_tx_out = debugger.tx_out;
method led = blinky.led_on;
endmodule endmodule
endpackage endpackage

View File

@ -14,3 +14,6 @@ LOCATE COMP "debug_serial_out" SITE "L4"; # FPGA transmits to ftdi
LOCATE COMP "debug_serial_in" SITE "M1"; # FPGA receives from ftdi LOCATE COMP "debug_serial_in" SITE "M1"; # FPGA receives from ftdi
IOBUF PORT "debug_serial_out" PULLMODE=UP IO_TYPE=LVCMOS33 DRIVE=4; IOBUF PORT "debug_serial_out" PULLMODE=UP IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "debug_serial_in" PULLMODE=UP IO_TYPE=LVCMOS33; IOBUF PORT "debug_serial_in" PULLMODE=UP IO_TYPE=LVCMOS33;
LOCATE COMP "led" SITE "B2";
IOBUF PORT "led" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;

View File

@ -42,7 +42,7 @@ module ECP5_RAM#(
.REGMODE_B(REGMODE_B), .REGMODE_B(REGMODE_B),
.WRITEMODE_B(WRITEMODE_B), .WRITEMODE_B(WRITEMODE_B),
.CSDECODE_B(CSDECODE_B) .CSDECODE_B(CSDECODE_B)
) ram(.CLKA(CLKA), .RSTA(RSTA), .CEA(CEA), .OCEA(OCEA), .WEA(WEA), ) ram(.CLKA(CLKA), .RSTA(!RSTA), .CEA(CEA), .OCEA(OCEA), .WEA(WEA),
.CSA2(CSA[2]), .CSA1(CSA[1]), .CSA0(CSA[0]), .CSA2(CSA[2]), .CSA1(CSA[1]), .CSA0(CSA[0]),
.ADA13(ADA[13]), .ADA12(ADA[12]), .ADA11(ADA[11]), .ADA10(ADA[10]), .ADA9(ADA[9]), .ADA13(ADA[13]), .ADA12(ADA[12]), .ADA11(ADA[11]), .ADA10(ADA[10]), .ADA9(ADA[9]),
.ADA8(ADA[8]), .ADA7(ADA[7]), .ADA6(ADA[6]), .ADA5(ADA[5]), .ADA4(ADA[4]), .ADA8(ADA[8]), .ADA7(ADA[7]), .ADA6(ADA[6]), .ADA5(ADA[5]), .ADA4(ADA[4]),
@ -56,7 +56,7 @@ module ECP5_RAM#(
.DOA7(DOA[7]), .DOA6(DOA[6]), .DOA5(DOA[5]), .DOA4(DOA[4]), .DOA3(DOA[3]), .DOA7(DOA[7]), .DOA6(DOA[6]), .DOA5(DOA[5]), .DOA4(DOA[4]), .DOA3(DOA[3]),
.DOA2(DOA[2]), .DOA1(DOA[1]), .DOA0(DOA[0]), .DOA2(DOA[2]), .DOA1(DOA[1]), .DOA0(DOA[0]),
.CLKB(CLKB), .RSTB(RSTB), .CEB(CEB), .OCEB(OCEB), .WEB(WEB), .CLKB(CLKB), .RSTB(!RSTB), .CEB(CEB), .OCEB(OCEB), .WEB(WEB),
.CSA2(CSA[2]), .CSA1(CSA[1]), .CSA0(CSA[0]), .CSA2(CSA[2]), .CSA1(CSA[1]), .CSA0(CSA[0]),
.ADB13(ADB[13]), .ADB12(ADB[12]), .ADB11(ADB[11]), .ADB10(ADB[10]), .ADB9(ADB[9]), .ADB13(ADB[13]), .ADB12(ADB[12]), .ADB11(ADB[11]), .ADB10(ADB[10]), .ADB9(ADB[9]),
.ADB8(ADB[8]), .ADB7(ADB[7]), .ADB6(ADB[6]), .ADB5(ADB[5]), .ADB4(ADB[4]), .ADB8(ADB[8]), .ADB7(ADB[7]), .ADB6(ADB[6]), .ADB5(ADB[5]), .ADB4(ADB[4]),

View File

@ -16,6 +16,8 @@ export VRAMAddr, VRAMData, VRAMRequest(..), VRAMResponse(..);
export VRAMServer(..); export VRAMServer(..);
export VRAM(..), mkVRAM; export VRAM(..), mkVRAM;
export mkArbitratedVRAMServers;
// A VRAMServer is a memory port. // A VRAMServer is a memory port.
typedef Server#(VRAMRequest, VRAMResponse) VRAMServer; typedef Server#(VRAMRequest, VRAMResponse) VRAMServer;
@ -44,8 +46,11 @@ module mkArbitratedVRAMServers(VRAMServer ram, MemArbiter#(n, VRAMAddr) arb, Vec
(* fire_when_enabled *) (* fire_when_enabled *)
rule submit (awaiting_response[1] matches tagged Invalid); rule submit (awaiting_response[1] matches tagged Invalid);
let port = arb.granted_port(); let port = arb.granted_port();
ram.request.put(requests[port].first); let req = requests[port].first;
ram.request.put(req);
requests[port].deq(); requests[port].deq();
// Only reads generate a response.
if (req.data matches tagged Invalid)
awaiting_response[1] <= tagged Valid port; awaiting_response[1] <= tagged Valid port;
endrule endrule

View File

@ -28,11 +28,11 @@ typedef EBR#(ByteAddr, VRAMData, ByteAddr, VRAMData) ByteRAM;
typedef struct { typedef struct {
VRAMAddr addr; VRAMAddr addr;
Maybe#(VRAMData) data; Maybe#(VRAMData) data;
} VRAMRequest deriving (Bits, Eq); } VRAMRequest deriving (Bits, Eq, FShow);
typedef struct { typedef struct {
VRAMData data; VRAMData data;
} VRAMResponse deriving (Bits, Eq); } VRAMResponse deriving (Bits, Eq, FShow);
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;

136
vram/VRAM_Test.bsv Normal file
View File

@ -0,0 +1,136 @@
package VRAM_Test;
import Assert::*;
import StmtFSM::*;
import GetPut::*;
import ClientServer::*;
import Connectable::*;
import Vector::*;
import MemArbiter::*;
import Testing::*;
import VRAM::*;
interface FakeVRAM;
interface VRAMServer server;
method Action next_response(VRAMResponse resp);
method Maybe#(VRAMRequest) last_request();
endinterface
module mkFakeVRAM(FakeVRAM);
Reg#(Maybe#(VRAMRequest)) req <- mkReg(tagged Invalid);
Reg#(Maybe#(VRAMResponse)) resp <- mkReg(tagged Invalid);
interface VRAMServer server;
interface Put request;
method Action put(r);
req <= tagged Valid r;
endmethod
endinterface
interface Get response;
method ActionValue#(VRAMResponse) get() if (resp matches tagged Valid .respval &&& req matches tagged Valid .reqval &&& reqval.data matches tagged Invalid);
resp <= tagged Invalid;
return respval;
endmethod
endinterface
endinterface
method Action next_response(r) if (resp matches tagged Invalid);
resp <= tagged Valid r;
endmethod
method Maybe#(VRAMRequest) last_request();
return req;
endmethod
endmodule
module mkArbitratedVRAMServersTest(FSM);
let testflags <- mkTestFlags();
let cycles <- mkCycleCounter();
let vram <- mkFakeVRAM();
MemArbiter#(3, VRAMAddr) arb <- mkPriorityMemArbiter();
Vector#(3, VRAMServer) ports <- mkArbitratedVRAMServers(vram.server, arb);
function Action check_read(VRAMServer port, VRAMAddr want_addr, VRAMData want_data);
return action
let want_request = VRAMRequest{addr: want_addr, data: tagged Invalid};
if (testflags.verbose)
$display("Last received VRAM request: ", fshow(vram.last_request), " want ", fshow(want_request));
dynamicAssert(vram.last_request == tagged Valid want_request, "wrong request seen by vram for read");
let got <- port.response.get();
if (testflags.verbose)
$display("VRAM.read() = %0d, want %0d", got.data, want_data);
dynamicAssert(got.data == want_data, "wrong data seen in vram read");
endaction;
endfunction
function Action check_write(VRAMAddr want_addr, VRAMData data);
return action
let want = VRAMRequest{addr: want_addr, data: tagged Valid data};
if (testflags.verbose)
$display("Last received VRAM request: ", fshow(vram.last_request), " want ", fshow(want));
dynamicAssert(vram.last_request == tagged Valid want, "wrong request seen by vram for write");
endaction;
endfunction
let fsm <- mkFSM(seq
// Single write
ports[1].request.put(VRAMRequest{addr: 123, data: tagged Valid 42});
check_write(123, 42);
// Single read
vram.next_response(VRAMResponse{data: 23});
ports[1].request.put(VRAMRequest{addr: 124, data: tagged Invalid});
check_read(ports[1], 124, 23);
// Concurrent ops, process port 1 response first to check
// buffering allows port 0 op to finish and port 1 to proceed
vram.next_response(VRAMResponse{data: 11});
par
vram.next_response(VRAMResponse{data: 66});
ports[0].request.put(VRAMRequest{addr: 123, data: tagged Invalid});
ports[1].request.put(VRAMRequest{addr: 125, data: tagged Invalid});
endpar
check_read(ports[1], 125, 66);
check_read(ports[0], 125, 11); // note, 125 because the last op is still port 1's read
endseq);
return fsm;
endmodule
module mkTestFull(FSM);
let testflags <- mkTestFlags();
let dut <- mkVRAM(4);
let fsm <- mkFSM(seq
dut.cpu.request.put(VRAMRequest{addr: 1, data: tagged Valid 42});
dut.cpu.request.put(VRAMRequest{addr: 1, data: tagged Invalid});
action
let resp <- dut.cpu.response.get();
if (testflags.verbose)
$display("vram read: ", fshow(resp));
dynamicAssert(resp.data == 42, "wrong data read after writing");
endaction
endseq);
return fsm;
endmodule
module mkTB();
let testGlue <- mkArbitratedVRAMServersTest();
let testFull <- mkTestFull();
runTest(100,
mkTest("VRAM", seq
mkTest("VRAM/Glue", seq
testGlue.start();
await(testGlue.done);
endseq);
mkTest("VRAM/Full", seq
testFull.start();
await(testFull.done);
endseq);
endseq));
endmodule
endpackage