systems/qf100: factor out common qf100 stuff, add testbench

main
q3k 2022-03-20 01:35:28 +01:00
parent d29cc9a38d
commit 200b8d229e
6 changed files with 8606 additions and 2 deletions

View File

@ -20,10 +20,12 @@ interface Lanai_BlockRAM#(numeric type n);
interface Lanai_Memory memory;
endinterface
module mkBlockMemory#(String filename) (Lanai_BlockRAM#(k)) provisos (Log#(k, n));
module mkBlockMemory#(Maybe#(String) filename) (Lanai_BlockRAM#(k)) provisos (Log#(k, n));
BRAM_Configure cfg = defaultValue;
cfg.latency = 1;
cfg.loadFormat = tagged Hex filename;
if (filename matches tagged Valid .fn) begin
cfg.loadFormat = tagged Hex fn;
end
cfg.outFIFODepth = 3;
cfg.allowWriteResponseBypass = False;
BRAM2PortBE#(Bit#(n), Bit#(32), 4) bram <- mkBRAM2ServerBE(cfg);

60
systems/qf100/BUILD.bazel Normal file
View File

@ -0,0 +1,60 @@
load("//build/bluespec:rules.bzl", "bluespec_library", "bluesim_test")
load("//build/synthesis:rules.bzl", "rtl_bundle")
bluespec_library(
name = "qf100",
srcs = [
"QF100.bsv",
],
deps = [
"//lanai:cpu",
"//lanai/frontend",
"//lanai/frontend:spi_flash_controller",
"//wishbone/peripherals:spi",
"//wishbone/peripherals:gpio",
],
synthesize = {
"QF100": [
"mkQF100",
"mkQF100Memory",
"mkQF100SPI",
"mkQF100GPIO",
"mkQF100Fabric",
],
},
)
bluespec_library(
name = "Tb",
srcs = [
"SPIFlashEmulator.bsv",
"Tb.bsv",
],
data = [
":flash.bin",
],
deps = [
":qf100",
],
synthesize = {
"Tb": [
"mkTbQF100",
],
},
)
bluesim_test(
name = "test_qf100",
deps = [
":Tb",
],
top = "mkTbQF100",
)
bluespec_library(
name = "spi_flash_emulator",
srcs = [
"SPIFlashEmulator.bsv",
],
)

125
systems/qf100/QF100.bsv Normal file
View File

@ -0,0 +1,125 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2022 Sergiusz Bazanski
package QF100;
import GetPut :: *;
import ClientServer :: *;
import Connectable :: *;
import Lanai_IFC :: *;
import Lanai_CPU :: *;
import Lanai_Memory :: *;
import LanaiFrontend :: *;
import SPIFlashController :: *;
import RAM :: *;
import WishboneCrossbar :: *;
import WishboneSPI :: *;
import WishboneGPIO :: *;
Bit#(1) wbAddrSPI = 0;
Bit#(1) wbAddrGPIO = 1;
function Maybe#(WishboneCrossbar::DecodedAddr#(2, 32)) decoder(Bit#(32) address);
return case (address) matches
32'h4001_30??: tagged Valid DecodedAddr { downstream: wbAddrSPI, address: address & 32'hff };
32'h4001_08??: tagged Valid DecodedAddr { downstream: wbAddrGPIO, address: address & 32'hff };
default: tagged Invalid;
endcase;
endfunction
(* synthesize *)
module mkQF100Memory(Lanai_BlockRAM#(2048));
Lanai_BlockRAM#(2048) inner <- mkBlockMemory(tagged Invalid);
return inner;
endmodule
(* synthesize *)
module mkQF100SPI(WishboneSPI::SPIController#(32));
let res <- mkSPIController;
interface slave = res.slave;
interface spiMaster = res.spiMaster;
endmodule
(* synthesize *)
module mkQF100GPIO(WishboneGPIO::GPIOController#(32));
let res <- mkGPIOController;
interface slave = res.slave;
method in = res.in;
method out = res.out;
method oe = res.oe;
endmodule
interface QF100Fabric;
interface Wishbone::Slave#(32, 32, 4) cpu;
interface Wishbone::Master#(32, 32, 4) spi;
interface Wishbone::Master#(32, 32, 4) gpio;
endinterface
(* synthesize *)
module mkQF100Fabric(QF100Fabric);
WishboneCrossbar::Crossbar#(1, 2, 32, 32, 4) fabric <- mkCrossbar(decoder);
interface cpu = fabric.upstreams[0];
interface spi = fabric.downstreams[wbAddrSPI];
interface gpio = fabric.downstreams[wbAddrGPIO];
endmodule
interface QF100;
// Memory SPI.
interface WishboneSPI::Master mspi;
(* always_ready *)
method Bool mspi_csb;
// General purpose SPI.
interface WishboneSPI::Master spi;
// GPIO.
(* always_ready *)
method Bit#(16) gpio_oe;
(* always_ready *)
method Bit#(16) gpio_out;
(* always_ready, always_enabled *)
method Action gpio_in(Bit#(16) value);
endinterface
(* synthesize *)
module mkQF100(QF100);
LanaiFrontend frontend <- mkLanaiFrontend;
Lanai_IFC cpu <- mkLanaiCPU;
mkConnection(cpu.imem_client, frontend.core_imem);
mkConnection(cpu.dmem_client, frontend.core_dmem);
Lanai_BlockRAM#(2048) ram <- mkQF100Memory;
mkConnection(frontend.ram_imem, ram.memory.imem);
mkConnection(frontend.ram_dmem, ram.memory.dmem);
SPIFlashController#(16, 64) fmc <- mkSPIFlashController;
mkConnection(frontend.fmc_imem, fmc.serverA);
rule fmcDMemTranslate;
let req <- frontend.fmc_dmem.request.get();
fmc.serverB.request.put(req.addr);
endrule
mkConnection(frontend.fmc_dmem.response, fmc.serverB.response);
QF100Fabric fabric <- mkQF100Fabric;
mkConnection(cpu.sysmem_client, fabric.cpu);
WishboneSPI::SPIController#(32) spiCtrl <- mkQF100SPI;
mkConnection(fabric.spi, spiCtrl.slave);
WishboneGPIO::GPIOController#(32) gpioCtrl <- mkQF100GPIO;
mkConnection(fabric.gpio, gpioCtrl.slave);
interface mspi = fmc.spi;
method Bool mspi_csb;
return unpack(fmc.csb);
endmethod
interface spi = spiCtrl.spiMaster;
method gpio_oe = gpioCtrl.oe;
method gpio_out = gpioCtrl.out;
method gpio_in = gpioCtrl.in;
endmodule
endpackage

View File

@ -0,0 +1,191 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2022 Sergiusz Bazanski
package SPIFlashEmulator;
import BRAM :: *;
import FIFO :: *;
import SpecialFIFOs :: *;
import StmtFSM :: *;
interface SPIFlashEmulator;
(* always_enabled, always_ready *)
method Action mosi(Bool value);
(* always_enabled *)
method Bool miso;
(* always_enabled, always_ready *)
method Action sclk(Bool value);
(* always_enabled, always_ready *)
method Action csb(Bool value);
endinterface
module mkSPIFlashEmulator#(String filename) (SPIFlashEmulator);
BRAM_Configure cfg = defaultValue;
cfg.latency = 1;
cfg.loadFormat = tagged Hex filename;
cfg.outFIFODepth = 3;
cfg.allowWriteResponseBypass = False;
BRAM1Port#(Bit#(12), Bit#(32)) bram <- mkBRAM1Server(cfg);
Reg#(Bool) csbReg <- mkReg(False);
Reg#(Bool) sclkReg <- mkReg(False);
Reg#(Bool) mosiReg <- mkReg(False);
Reg#(Bit#(8)) incomingByte <- mkReg(0);
Reg#(Maybe#(Bit#(3))) bitNo <- mkReg(tagged Invalid);
rule startReceive(bitNo matches tagged Invalid &&& csbReg == False);
bitNo <= tagged Valid 0;
endrule
Reg#(Bool) prevSclk <- mkReg(False);
rule updatePrevSclk;
prevSclk <= sclkReg;
endrule
FIFO#(Maybe#(Bit#(8))) pendingByte <- mkPipelineFIFO;
Bool clockRisen = prevSclk == False && sclkReg == True;
rule onClockRisen(bitNo matches tagged Valid .bn &&& csbReg == False &&& clockRisen);
let val = incomingByte;
val[7-bn] = pack(mosiReg);
if (bn == 7) begin
pendingByte.enq(tagged Valid val);
bitNo <= tagged Invalid;
end else begin
incomingByte <= val;
bitNo <= tagged Valid (bn + 1);
end
endrule
rule onCSBHigh(bitNo matches tagged Valid .bn &&& csbReg == True);
pendingByte.enq(tagged Invalid);
bitNo <= tagged Invalid;
endrule
Reg#(Bit#(8)) command <- mkReg(0);
Reg#(Bool) failed <- mkReg(False);
Reg#(Bit#(24)) readAddr <- mkReg(0);
Reg#(Bit#(24)) readAddrWait <- mkReg(0);
Reg#(Bit#(32)) bramBuf <- mkReg(0);
Reg#(Bit#(24)) bramAddr <- mkReg('h1);
Reg#(Bit#(8)) sending <- mkReg(0);
Stmt fsm = seq
while (True) seq
failed <= False;
action
pendingByte.deq();
case (pendingByte.first) matches
tagged Valid .v: command <= v;
tagged Invalid: failed <= True;
endcase
endaction
if (failed) continue;
$display("Flash emulator: recv %x", command);
if (command == 3) seq
action
pendingByte.deq();
case (pendingByte.first) matches
tagged Valid .v: readAddr[23:16] <= v;
tagged Invalid: failed <= True;
endcase
endaction
if (failed) continue;
action
pendingByte.deq();
case (pendingByte.first) matches
tagged Valid .v: readAddr[15:8] <= v;
tagged Invalid: failed <= True;
endcase
endaction
if (failed) continue;
action
pendingByte.deq();
case (pendingByte.first) matches
tagged Valid .v: readAddr[7:0] <= v;
tagged Invalid: failed <= True;
endcase
endaction
if (failed) continue;
par
while (!failed) seq
while (readAddr[1:0] != 0 && failed == False) seq
noAction;
endseq
readAddrWait <= readAddr;
bram.portA.request.put(BRAMRequest { responseOnWrite: True
, address: readAddrWait[13:2]
, datain: 0
, write: False
});
action
let bramRes <- bram.portA.response.get();
action
bramBuf <= bramRes;
bramAddr <= readAddrWait;
endaction
endaction
endseq
seq
while (bramAddr != readAddr) seq
noAction;
endseq
action
let byteInBuf = (7 - (readAddr & 'b111)) << 3;
sending <= bramBuf[byteInBuf+7:byteInBuf];
endaction
$display("Flash emulator: read addr: %x", readAddr);
while (!failed) seq
action
case (pendingByte.first) matches
tagged Invalid: failed <= True;
endcase
pendingByte.deq();
endaction
seq
readAddr <= readAddr + 1;
while (bramAddr != (readAddr & (~'b11))) seq
noAction;
endseq
action
let byteInBuf = (7 - (readAddr & 'b111)) << 3;
sending <= bramBuf[byteInBuf+7:byteInBuf];
endaction
endseq
endseq
$display("Flash emulator: read done");
endseq
endpar
endseq else seq
$display("Flash emulator: unhandled command %x", command);
endseq
endseq
endseq;
mkAutoFSM(fsm);
method Action mosi(Bool value);
mosiReg <= value;
endmethod
method Action sclk(Bool value);
sclkReg <= value;
endmethod
method Action csb(Bool value);
csbReg <= value;
endmethod
method Bool miso;
return case (bitNo) matches
tagged Valid .bn: unpack(sending[7-bn]);
tagged Invalid: False;
endcase;
endmethod
endmodule
endpackage

34
systems/qf100/Tb.bsv Normal file
View File

@ -0,0 +1,34 @@
package Tb;
import QF100 :: *;
import SPIFlashEmulator :: *;
import StmtFSM :: *;
import WishboneSPI :: *;
(* synthesize *)
module mkTbQF100(Empty);
QF100 qf100 <- mkQF100;
SPIFlashEmulator emu <- mkSPIFlashEmulator("systems/qf100/flash.bin");
rule feed_qf100_in;
qf100.gpio_in(0);
qf100.spi.miso(False);
qf100.mspi.miso(emu.miso);
endrule
rule feed_emu_in;
emu.mosi(unpack(qf100.mspi.mosi));
emu.sclk(unpack(qf100.mspi.sclk));
emu.csb(qf100.mspi_csb);
endrule
Reg#(Bit#(32)) tmp <- mkReg(0);
Stmt test = seq
for (tmp <= 0; tmp <= 20000; tmp <= tmp + 1) seq
noAction;
endseq
endseq;
mkAutoFSM(test);
endmodule
endpackage

8192
systems/qf100/flash.bin Normal file

File diff suppressed because it is too large Load Diff