mirror of
https://github.com/enjoy-digital/litex.git
synced 2025-01-04 09:52:26 -05:00
litesata: add mirroring
This commit is contained in:
parent
c3716296ae
commit
0d1a7b9315
5 changed files with 402 additions and 0 deletions
98
misoclib/mem/litesata/example_designs/targets/mirroring.py
Normal file
98
misoclib/mem/litesata/example_designs/targets/mirroring.py
Normal file
|
@ -0,0 +1,98 @@
|
|||
from misoclib.mem.litesata.common import *
|
||||
from migen.genlib.cdc import *
|
||||
from migen.genlib.resetsync import AsyncResetSynchronizer
|
||||
from migen.bank.description import *
|
||||
|
||||
from misoclib.soc import SoC
|
||||
|
||||
from misoclib.tools.litescope.common import *
|
||||
from misoclib.tools.litescope.frontend.la import LiteScopeLA
|
||||
from misoclib.tools.litescope.core.port import LiteScopeTerm
|
||||
|
||||
from misoclib.com.uart.bridge import UARTWishboneBridge
|
||||
|
||||
from misoclib.mem.litesata.common import *
|
||||
from misoclib.mem.litesata.phy import LiteSATAPHY
|
||||
from misoclib.mem.litesata.core import LiteSATACore
|
||||
from misoclib.mem.litesata.frontend.crossbar import LiteSATACrossbar
|
||||
from misoclib.mem.litesata.frontend.mirroring import LiteSATAMirroring
|
||||
from misoclib.mem.litesata.frontend.bist import LiteSATABIST
|
||||
|
||||
from misoclib.mem.litesata.example_designs.targets.bist import CRG, StatusLeds
|
||||
|
||||
|
||||
class MirroringSoC(SoC, AutoCSR):
|
||||
default_platform = "kc705"
|
||||
csr_map = {
|
||||
"sata_bist0": 16,
|
||||
"sata_bist1": 17,
|
||||
"sata_bist2": 18,
|
||||
"sata_bist3": 19,
|
||||
}
|
||||
csr_map.update(SoC.csr_map)
|
||||
def __init__(self, platform):
|
||||
clk_freq = 166*1000000
|
||||
SoC.__init__(self, platform, clk_freq,
|
||||
cpu_type="none",
|
||||
with_csr=True, csr_data_width=32,
|
||||
with_uart=False,
|
||||
with_identifier=True,
|
||||
with_timer=False
|
||||
)
|
||||
self.add_cpu_or_bridge(UARTWishboneBridge(platform.request("serial"), clk_freq, baudrate=115200))
|
||||
self.add_wb_master(self.cpu_or_bridge.wishbone)
|
||||
self.submodules.crg = CRG(platform)
|
||||
|
||||
# SATA PHYs
|
||||
sata_phy0 = LiteSATAPHY(platform.device, platform.request("sata_clocks"), platform.request("sata", 0), "sata_gen2", clk_freq)
|
||||
sata_phy1 = LiteSATAPHY(platform.device, sata_phy0.crg.refclk, platform.request("sata", 1), "sata_gen2", clk_freq)
|
||||
sata_phy2 = LiteSATAPHY(platform.device, sata_phy0.crg.refclk, platform.request("sata", 2), "sata_gen2", clk_freq)
|
||||
sata_phy3 = LiteSATAPHY(platform.device, sata_phy0.crg.refclk, platform.request("sata", 3), "sata_gen2", clk_freq)
|
||||
sata_phys = [sata_phy0, sata_phy1, sata_phy2, sata_phy3]
|
||||
for i, sata_phy in enumerate(sata_phys):
|
||||
sata_phy = RenameClockDomains(sata_phy, {"sata_rx": "sata_rx{}".format(str(i)),
|
||||
"sata_tx": "sata_tx{}".format(str(i))})
|
||||
setattr(self.submodules, "sata_phy{}".format(str(i)), sata_phy)
|
||||
|
||||
# SATA Cores
|
||||
self.submodules.sata_core0 = LiteSATACore(self.sata_phy0)
|
||||
self.submodules.sata_core1 = LiteSATACore(self.sata_phy1)
|
||||
self.submodules.sata_core2 = LiteSATACore(self.sata_phy2)
|
||||
self.submodules.sata_core3 = LiteSATACore(self.sata_phy3)
|
||||
sata_cores = [self.sata_core0, self.sata_core1, self.sata_core2, self.sata_core3]
|
||||
|
||||
# SATA Frontend
|
||||
self.submodules.sata_mirroring = LiteSATAMirroring(sata_cores)
|
||||
self.submodules.sata_crossbar0 = LiteSATACrossbar(self.sata_mirroring.ports[0])
|
||||
self.submodules.sata_crossbar1 = LiteSATACrossbar(self.sata_mirroring.ports[1])
|
||||
self.submodules.sata_crossbar2 = LiteSATACrossbar(self.sata_mirroring.ports[2])
|
||||
self.submodules.sata_crossbar3 = LiteSATACrossbar(self.sata_mirroring.ports[3])
|
||||
|
||||
# SATA Application
|
||||
self.submodules.sata_bist0 = LiteSATABIST(self.sata_crossbar0, with_csr=True)
|
||||
self.submodules.sata_bist1 = LiteSATABIST(self.sata_crossbar1, with_csr=True)
|
||||
self.submodules.sata_bist2 = LiteSATABIST(self.sata_crossbar2, with_csr=True)
|
||||
self.submodules.sata_bist3 = LiteSATABIST(self.sata_crossbar3, with_csr=True)
|
||||
|
||||
# Status Leds
|
||||
self.submodules.status_leds = StatusLeds(platform, sata_phys)
|
||||
|
||||
|
||||
platform.add_platform_command("""
|
||||
create_clock -name sys_clk -period 6 [get_nets sys_clk]
|
||||
""")
|
||||
|
||||
for i in range(len(sata_phys)):
|
||||
platform.add_platform_command("""
|
||||
create_clock -name {sata_rx_clk} -period 6.66 [get_nets {sata_rx_clk}]
|
||||
create_clock -name {sata_tx_clk} -period 6.66 [get_nets {sata_tx_clk}]
|
||||
|
||||
set_false_path -from [get_clocks sys_clk] -to [get_clocks {sata_rx_clk}]
|
||||
set_false_path -from [get_clocks sys_clk] -to [get_clocks {sata_tx_clk}]
|
||||
set_false_path -from [get_clocks {sata_rx_clk}] -to [get_clocks sys_clk]
|
||||
set_false_path -from [get_clocks {sata_tx_clk}] -to [get_clocks sys_clk]
|
||||
""".format(sata_rx_clk="sata_rx{}_clk".format(str(i)),
|
||||
sata_tx_clk="sata_tx{}_clk".format(str(i))))
|
||||
|
||||
|
||||
default_subtarget = MirroringSoC
|
43
misoclib/mem/litesata/example_designs/test/test_mirroring.py
Normal file
43
misoclib/mem/litesata/example_designs/test/test_mirroring.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
import sys
|
||||
from bist import *
|
||||
from misoclib.tools.litescope.software.driver.la import LiteScopeLADriver
|
||||
|
||||
|
||||
def main(wb):
|
||||
identifys = []
|
||||
generators = []
|
||||
checkers = []
|
||||
for i in range(4):
|
||||
identifys.append(LiteSATABISTIdentifyDriver(wb.regs, "sata_bist{:d}".format(i)))
|
||||
generators.append(LiteSATABISTGeneratorDriver(wb.regs, "sata_bist{:d}".format(i)))
|
||||
checkers.append(LiteSATABISTCheckerDriver(wb.regs, "sata_bist{:d}".format(i)))
|
||||
|
||||
wb.open()
|
||||
regs = wb.regs
|
||||
# # #
|
||||
|
||||
print("Identify HDDs:")
|
||||
print("-"*80)
|
||||
for i, identify in enumerate(identifys):
|
||||
identify.run()
|
||||
print("HDD{:d}:".format(i))
|
||||
print("-"*40)
|
||||
identify.hdd_info()
|
||||
print("")
|
||||
|
||||
print("Test Mirroring:")
|
||||
print("-"*80)
|
||||
errors = 0
|
||||
for sector in range(8):
|
||||
# Write with one generator, verify with all checkers.
|
||||
# Use generator number as sector offset to ensure we
|
||||
# are not reading data written by another generator.
|
||||
for offset, generator in enumerate(generators):
|
||||
generator.run(sector + offset, 1024, 1, 1)
|
||||
for checker in checkers:
|
||||
a, e, s = checker.run(sector + offset, 1024, 1, 1)
|
||||
errors += e
|
||||
print("errors {:d}".format(errors))
|
||||
|
||||
# # #
|
||||
wb.close()
|
182
misoclib/mem/litesata/frontend/mirroring.py
Normal file
182
misoclib/mem/litesata/frontend/mirroring.py
Normal file
|
@ -0,0 +1,182 @@
|
|||
from migen.actorlib.packet import Arbiter, Dispatcher, Status
|
||||
from migen.flow.plumbing import Multiplexer
|
||||
|
||||
from misoclib.mem.litesata.common import *
|
||||
from misoclib.mem.litesata.frontend.common import *
|
||||
from misoclib.mem.litesata.frontend.striping import LiteSATAStripingTX, LiteSATAStripingRX
|
||||
|
||||
|
||||
class LiteSATAMirroringCtrl(Module):
|
||||
def __init__(self, n):
|
||||
self.new_cmds = Signal(n)
|
||||
self.ack_cmds = Signal(n)
|
||||
|
||||
self.reading = Signal()
|
||||
self.writing = Signal()
|
||||
|
||||
self.wants_write = Signal()
|
||||
self.write_sel = Signal(max=n)
|
||||
|
||||
# # #
|
||||
|
||||
pending_cmds = Signal(n)
|
||||
self.sync += pending_cmds.eq(self.new_cmds | (pending_cmds & ~self.ack_cmds))
|
||||
can_commute = Signal()
|
||||
self.comb += can_commute.eq((pending_cmds | self.new_cmds) == 0)
|
||||
|
||||
self.fsm = fsm = FSM(reset_state="READ")
|
||||
self.submodules += fsm
|
||||
fsm.act("READ",
|
||||
self.reading.eq(1),
|
||||
If(self.wants_write & can_commute,
|
||||
NextState("WRITE")
|
||||
)
|
||||
)
|
||||
fsm.act("WRITE",
|
||||
self.writing.eq(1),
|
||||
If(~self.wants_write & can_commute,
|
||||
NextState("READ")
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class LiteSATAMirroringTX(Module):
|
||||
def __init__(self, n, dw, ctrl):
|
||||
self.sinks = sinks = [Sink(command_tx_description(dw)) for i in range(n)]
|
||||
self.sources = sources = [Source(command_tx_description(dw)) for i in range(n)]
|
||||
|
||||
# # #
|
||||
|
||||
wants_write = Signal()
|
||||
|
||||
reading = Signal()
|
||||
writing = Signal()
|
||||
|
||||
reads = [Sink(command_tx_description(dw)) for i in range(dw)]
|
||||
writes = [Sink(command_tx_description(dw)) for i in range(dw)]
|
||||
for sink, read, write in zip(sinks, reads, writes):
|
||||
read_stall = Signal()
|
||||
read_status = Status(read)
|
||||
self.submodules += read_status
|
||||
self.comb += [
|
||||
Record.connect(sink, read, leave_out=["stb", "ack"]),
|
||||
Record.connect(sink, write, leave_out=["stb", "ack"]),
|
||||
read.stb.eq(sink.stb & (sink.read | sink.identify) & ~read_stall),
|
||||
write.stb.eq(sink.stb & sink.write),
|
||||
If(sink.read | sink.identify,
|
||||
sink.ack.eq((read.ack & ~read_stall))
|
||||
).Else(
|
||||
sink.ack.eq(write.ack)
|
||||
)
|
||||
]
|
||||
self.sync += \
|
||||
If(~ctrl.wants_write,
|
||||
read_stall.eq(0)
|
||||
).Elif(~read_status.ongoing,
|
||||
read_stall.eq(1)
|
||||
)
|
||||
|
||||
write_striper = LiteSATAStripingTX(n, dw, mirroring_mode=True)
|
||||
write_arbiter = Arbiter(writes, write_striper.sink)
|
||||
self.submodules += write_striper, write_arbiter
|
||||
|
||||
for i in range(n):
|
||||
source_status = Status(sources[i])
|
||||
self.submodules += source_status
|
||||
self.comb += [
|
||||
If(ctrl.reading,
|
||||
Record.connect(reads[i], sources[i]) # independent reads
|
||||
).Elif(ctrl.writing,
|
||||
Record.connect(write_striper.sources[i], sources[i]) # identical writes
|
||||
),
|
||||
ctrl.new_cmds[i].eq(source_status.eop)
|
||||
]
|
||||
write_striper_sink_status = Status(write_striper.sink)
|
||||
self.submodules += write_striper_sink_status
|
||||
self.comb += [
|
||||
ctrl.wants_write.eq(write_striper_sink_status.ongoing),
|
||||
ctrl.write_sel.eq(write_arbiter.rr.grant)
|
||||
]
|
||||
|
||||
|
||||
class LiteSATAMirroringRX(Module):
|
||||
def __init__(self, n, dw, ctrl):
|
||||
self.sinks = sinks = [Sink(command_rx_description(dw)) for i in range(n)]
|
||||
self.sources = sources = [Source(command_rx_description(dw)) for i in range(n)]
|
||||
|
||||
# # #
|
||||
|
||||
muxs = [Multiplexer(command_rx_description(dw), 2) for i in range(n)]
|
||||
self.submodules += muxs
|
||||
|
||||
writes = [mux.sink0 for mux in muxs]
|
||||
reads = [mux.sink1 for mux in muxs]
|
||||
|
||||
for mux, source in zip(muxs, sources):
|
||||
self.comb += [
|
||||
mux.sel.eq(ctrl.reading),
|
||||
Record.connect(mux.source, source)
|
||||
]
|
||||
|
||||
write_striper = LiteSATAStripingRX(n, dw, mirroring_mode=True)
|
||||
write_dispatcher = Dispatcher(write_striper.source, writes)
|
||||
self.comb += write_dispatcher.sel.eq(ctrl.write_sel)
|
||||
self.submodules += write_striper, write_dispatcher
|
||||
|
||||
for i in range(n):
|
||||
sink_status = Status(sinks[i])
|
||||
self.submodules += sink_status
|
||||
self.comb += [
|
||||
Record.connect(sinks[i], reads[i], leave_out=["stb", "ack"]),
|
||||
Record.connect(sinks[i], write_striper.sinks[i], leave_out=["stb", "ack"]),
|
||||
reads[i].stb.eq(sinks[i].stb & ctrl.reading),
|
||||
write_striper.sinks[i].stb.eq(sinks[i].stb & ctrl.writing),
|
||||
sinks[i].ack.eq(reads[i].ack | write_striper.sinks[i].ack),
|
||||
ctrl.ack_cmds[i].eq(sink_status.eop & sinks[i].last)
|
||||
]
|
||||
|
||||
|
||||
class LiteSATAMirroring(Module):
|
||||
"""SATA Mirroring
|
||||
|
||||
The mirroring module handles N controllers and provides N ports.
|
||||
Each port has its dedicated controller for reads:
|
||||
port0 <----> controller0
|
||||
portX <----> controllerX
|
||||
portN <----> controllerN
|
||||
|
||||
Writes are mirrored on each controller:
|
||||
(port0 write) | (portN write)
|
||||
port0 ----------+----> controller0 | port0 (stalled) +-----> controller0
|
||||
portX (stalled) +----> controllerX | portX (stalled) +-----> controllerX
|
||||
portN (stalled) +----> controllerN | portN ----------+-----> controllerN
|
||||
|
||||
Writes have priority on reads. When a write is presented on one of the port, the
|
||||
module waits for all ongoing reads to finish and commute to write mode. Once all writes are
|
||||
serviced it returns to read mode.
|
||||
|
||||
Characteristics:
|
||||
- port's visible capacity = controller's visible capacity
|
||||
- total writes throughput = (slowest) controller's throughput
|
||||
- total reads throughput = N x controller's throughput
|
||||
|
||||
Can be used for data redundancy and/or to increase total reads speed.
|
||||
"""
|
||||
def __init__(self, controllers):
|
||||
n = len(controllers)
|
||||
dw = flen(controllers[0].sink.data)
|
||||
self.ports = [LiteSATAUserPort(dw) for i in range(n)]
|
||||
|
||||
# # #
|
||||
|
||||
self.submodules.ctrl = LiteSATAMirroringCtrl(n)
|
||||
self.submodules.tx = LiteSATAMirroringTX(n, dw, self.ctrl)
|
||||
self.submodules.rx = LiteSATAMirroringRX(n, dw, self.ctrl)
|
||||
for i in range(n):
|
||||
self.comb += [
|
||||
Record.connect(self.ports[i].sink, self.tx.sinks[i]),
|
||||
Record.connect(self.tx.sources[i], controllers[i].sink),
|
||||
|
||||
Record.connect(controllers[i].source, self.rx.sinks[i]),
|
||||
Record.connect(self.rx.sources[i], self.ports[i].source)
|
||||
]
|
|
@ -32,5 +32,8 @@ bist_tb:
|
|||
striping_tb:
|
||||
$(CMD) striping_tb.py
|
||||
|
||||
mirroring_tb:
|
||||
$(CMD) mirroring_tb.py
|
||||
|
||||
clean:
|
||||
rm crc scrambler *.vcd
|
||||
|
|
76
misoclib/mem/litesata/test/mirroring_tb.py
Normal file
76
misoclib/mem/litesata/test/mirroring_tb.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
from misoclib.mem.litesata.common import *
|
||||
from misoclib.mem.litesata.core import LiteSATACore
|
||||
from misoclib.mem.litesata.frontend.common import *
|
||||
from misoclib.mem.litesata.frontend.crossbar import LiteSATACrossbar
|
||||
from misoclib.mem.litesata.frontend.bist import LiteSATABISTGenerator, LiteSATABISTChecker
|
||||
from misoclib.mem.litesata.frontend.mirroring import LiteSATAMirroring
|
||||
|
||||
from misoclib.mem.litesata.test.common import *
|
||||
from misoclib.mem.litesata.test.model.hdd import *
|
||||
|
||||
|
||||
class TB(Module):
|
||||
def __init__(self):
|
||||
self.submodules.hdd0 = HDD(n=0,
|
||||
link_debug=False, link_random_level=0,
|
||||
transport_debug=False, transport_loopback=False,
|
||||
hdd_debug=True)
|
||||
self.submodules.core0 = LiteSATACore(self.hdd0.phy)
|
||||
|
||||
self.submodules.hdd1 = HDD(n=1,
|
||||
link_debug=False, link_random_level=0,
|
||||
transport_debug=False, transport_loopback=False,
|
||||
hdd_debug=True)
|
||||
self.submodules.core1 = LiteSATACore(self.hdd1.phy)
|
||||
|
||||
self.submodules.mirroring = LiteSATAMirroring([self.core0, self.core1])
|
||||
|
||||
self.submodules.crossbar0 = LiteSATACrossbar(self.mirroring.ports[0])
|
||||
self.submodules.generator0 = LiteSATABISTGenerator(self.crossbar0.get_port())
|
||||
self.submodules.checker0 = LiteSATABISTChecker(self.crossbar0.get_port())
|
||||
|
||||
self.submodules.crossbar1 = LiteSATACrossbar(self.mirroring.ports[1])
|
||||
self.submodules.generator1 = LiteSATABISTGenerator(self.crossbar1.get_port())
|
||||
self.submodules.checker1 = LiteSATABISTChecker(self.crossbar1.get_port())
|
||||
|
||||
def gen_simulation(self, selfp):
|
||||
hdd0 = self.hdd0
|
||||
hdd0.malloc(0, 64)
|
||||
hdd1 = self.hdd1
|
||||
hdd1.malloc(0, 64)
|
||||
sector = 0
|
||||
count = 1
|
||||
checker0 = selfp.checker0
|
||||
checker1 = selfp.checker1
|
||||
while True:
|
||||
for generator in [selfp.generator0, selfp.generator1]:
|
||||
# write data (alternate generators)
|
||||
generator.sector = sector
|
||||
generator.count = count
|
||||
generator.start = 1
|
||||
yield
|
||||
generator.start = 0
|
||||
yield
|
||||
while generator.done == 0:
|
||||
yield
|
||||
|
||||
# verify data on the 2 hdds in //
|
||||
checker0.sector = sector
|
||||
checker0.count = count
|
||||
checker0.start = 1
|
||||
checker1.sector = sector
|
||||
checker1.count = count
|
||||
checker1.start = 1
|
||||
yield
|
||||
checker0.start = 0
|
||||
checker1.start = 0
|
||||
yield
|
||||
while (checker0.done == 0) or (checker1.done == 0):
|
||||
yield
|
||||
print("errors {}".format(checker0.errors + checker1.errors))
|
||||
|
||||
# prepare next iteration
|
||||
sector += 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_simulation(TB(), ncycles=4096, vcd_name="my.vcd", keep_files=True)
|
Loading…
Reference in a new issue