From: Luke Kenneth Casson Leighton Date: Thu, 20 Aug 2020 14:28:12 +0000 (+0100) Subject: bugfix wishbone downconvert using wb sram 64-to-32 test X-Git-Tag: semi_working_ecp5~288^2~6 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=891c8706c53347b377bc2bcb3c7fd01a14ee7522;p=soc.git bugfix wishbone downconvert using wb sram 64-to-32 test --- diff --git a/src/soc/bus/test/test_sram_wb_downconvert.py b/src/soc/bus/test/test_sram_wb_downconvert.py new file mode 100644 index 00000000..fe87bf49 --- /dev/null +++ b/src/soc/bus/test/test_sram_wb_downconvert.py @@ -0,0 +1,132 @@ +"""demonstration of nmigen-soc SRAM behind a wishbone bus and a downconverter +""" +from nmigen_soc.wishbone.bus import Interface +from nmigen_soc.wishbone.sram import SRAM +from nmigen import Memory, Signal, Module +from nmigen.utils import log2_int +from soc.bus.wb_downconvert import WishboneDownConvert + +# memory +memory = Memory(width=32, depth=32) +sram = SRAM(memory=memory, granularity=16) + +# interface for converter +cvtbus = Interface(addr_width=log2_int(memory.depth//2, need_pow2=False), + data_width=memory.width*2, + features={'cti'}, + granularity=16) + +# actual converter +downcvt = WishboneDownConvert(cvtbus, sram.bus) +bus = cvtbus + +# valid wishbone signals include +# sram.bus.adr +# sram.bus.dat_w +# sram.bus.dat_r +# sram.bus.sel +# sram.bus.cyc +# sram.bus.stb +# sram.bus.we +# sram.bus.ack + +# setup simulation +from nmigen.back.pysim import Simulator, Delay, Settle +m = Module() +m.submodules.sram = sram +m.submodules.downcvt = downcvt +sim = Simulator(m) +sim.add_clock(1e-6) + +def print_sig(sig, format=None): + if format == None: + print(f"{sig.__repr__()} = {(yield sig)}") + if format == "h": + print(f"{sig.__repr__()} = {hex((yield sig))}") + +def process(): + + test_data = 0xdeadbeef12345678 + + # enable necessary signals for write + for en in range(4): + yield bus.sel[en].eq(1) + yield bus.we.eq(1) + yield bus.cyc.eq(1) + yield bus.stb.eq(1) + + # put data and address on bus + yield bus.adr.eq(0x4) + yield bus.dat_w.eq(test_data) + yield + + while True: + ack = yield bus.ack + if ack: + break + yield + yield bus.cyc.eq(0) + yield bus.stb.eq(0) + yield bus.adr.eq(0) + yield bus.dat_w.eq(0) + + + # set necessary signal to read bus + # at address 0 + yield bus.we.eq(0) + yield bus.adr.eq(0) + yield bus.cyc.eq(1) + yield bus.stb.eq(1) + yield + + while True: + ack = yield bus.ack + if ack: + break + yield + + # see sync_behaviors.py + # for why we need Settle() + # debug print the bus address/data + yield Settle() + yield from print_sig(bus.adr) + yield from print_sig(bus.dat_r, "h") + + # check the result + data = yield bus.dat_r + assert data == 0 + + # set necessary signal to read bus + # at address 4 + yield bus.we.eq(0) + yield bus.adr.eq(0x4) + yield bus.cyc.eq(1) + yield bus.stb.eq(1) + yield + + while True: + ack = yield bus.ack + if ack: + break + yield + + data = yield bus.dat_r + print ("data", hex(data)) + + yield from print_sig(bus.adr) + yield from print_sig(bus.dat_r, "h") + + # check the result + assert data == test_data, "data != %x %16x" % (test_data, data) + + # disable signals + yield bus.adr.eq(0) + yield bus.cyc.eq(0) + yield bus.stb.eq(0) + yield + +sim_writer = sim.write_vcd(f"{__file__[:-3]}.vcd") + +with sim_writer: + sim.add_sync_process(process) + sim.run() diff --git a/src/soc/bus/wb_downconvert.py b/src/soc/bus/wb_downconvert.py new file mode 100644 index 00000000..1e4389c2 --- /dev/null +++ b/src/soc/bus/wb_downconvert.py @@ -0,0 +1,122 @@ +from nmigen import Elaboratable, Module, Signal, Repl, Cat, Mux +from nmigen.utils import log2_int + +class WishboneDownConvert(Elaboratable): + """DownConverter + + This module splits Wishbone accesses from a master interface to a smaller + slave interface. + + Writes: + Writes from master are split N writes to the slave. Access + is acked when the last access is acked by the slave. + + Reads: + Read from master are split in N reads to the the slave. + Read data from the slave are cached before being presented, + concatenated on the last access. + + TODO: + Manage err signal? (Not implemented since we generally don't + use it on Migen/MiSoC modules) + """ + def __init__(self, master, slave): + self.master = master + self.slave = slave + + def elaborate(self, platform): + + master = self.master + slave = self.slave + m = Module() + comb = m.d.comb + sync = m.d.sync + + dw_from = len(master.dat_r) + dw_to = len(slave.dat_w) + ratio = dw_from//dw_to + + # # # + + read = Signal() + write = Signal() + + cached_data = Signal(dw_from) + shift_reg = Signal(dw_from) + + counter = Signal(log2_int(ratio, False)) + counter_reset = Signal() + counter_ce = Signal() + with m.If(counter_reset): + sync += counter.eq(0) + with m.Elif(counter_ce): + sync += counter.eq(counter + 1) + + counter_done = Signal() + comb += counter_done.eq(counter == ratio-1) + + # Main FSM + with m.FSM() as fsm: + with m.State("IDLE"): + comb += counter_reset.eq(1) + sync += cached_data.eq(0) + with m.If(master.stb & master.cyc): + with m.If(master.we): + m.next = "WRITE" + with m.Else(): + m.next = "READ" + + with m.State("WRITE"): + comb += write.eq(1) + comb += slave.we.eq(1) + comb += slave.cyc.eq(1) + with m.If(master.stb & master.cyc): + comb += slave.stb.eq(1) + with m.If(slave.ack): + comb += counter_ce.eq(1) + with m.If(counter_done): + comb += master.ack.eq(1) + m.next = "IDLE" + with m.Elif(~master.cyc): + m.next = "IDLE" + + with m.State("READ"): + comb += read.eq(1) + comb += slave.cyc.eq(1) + with m.If(master.stb & master.cyc): + comb += slave.stb.eq(1) + with m.If(slave.ack): + comb += counter_ce.eq(1) + with m.If(counter_done): + comb += master.ack.eq(1) + comb += master.dat_r.eq(shift_reg) + m.next = "IDLE" + with m.Elif(~master.cyc): + m.next = "IDLE" + + # Address + if hasattr(slave, 'cti'): + with m.If(counter_done): + comb += slave.cti.eq(7) # indicate end of burst + with m.Else(): + comb += slave.cti.eq(2) + comb += slave.adr.eq(Cat(counter, master.adr)) + + # write Datapath - select fragments of data, depending on "counter" + with m.Switch(counter): + slen = slave.sel.width + for i in range(ratio): + with m.Case(i): + # select fractions of dat_w and associated "sel" bits + print ("sel", i, "from", i*slen, "to", (i+1)*slen) + comb += slave.sel.eq(master.sel[i*slen:(i+1)*slen]) + comb += slave.dat_w.eq(master.dat_w[i*dw_to:(i+1)*dw_to]) + + # read Datapath - uses cached_data and master.dat_r as a shift-register. + # by the time "counter" is done (counter_done) this is complete + comb += shift_reg.eq(Cat(cached_data[dw_to:], slave.dat_r)) + with m.If(read & counter_ce): + sync += cached_data.eq(shift_reg) + + + return m diff --git a/src/soc/bus/wb_upconvert.py b/src/soc/bus/wb_upconvert.py deleted file mode 100644 index 666e488a..00000000 --- a/src/soc/bus/wb_upconvert.py +++ /dev/null @@ -1,118 +0,0 @@ -from nmigen import Elaboratable, Module, Signal, Repl, Cat, Mux -from nmigen.utils import log2_int - -class WishboneDownConvert(Elaboratable): - """DownConverter - - This module splits Wishbone accesses from a master interface to a smaller - slave interface. - - Writes: - Writes from master are split N writes to the slave. Access - is acked when the last access is acked by the slave. - - Reads: - Read from master are split in N reads to the the slave. - Read data from the slave are cached before being presented, - concatenated on the last access. - - TODO: - Manage err signal? (Not implemented since we generally don't - use it on Migen/MiSoC modules) - """ - def __init__(self, master, slave): - self.master = master - self.slave = slave - - def elaborate(self, platform): - - master = self.master - slave = self.slave - m = Module() - comb = m.d.comb - sync = m.d.sync - - dw_from = len(master.dat_r) - dw_to = len(slave.dat_w) - ratio = dw_from//dw_to - - # # # - - read = Signal() - write = Signal() - - cached_data = Signal(dw_from) - shift_reg = Signal(dw_from) - - counter = Signal(log2_int(ratio, False)+1) - counter_reset = Signal() - counter_ce = Signal() - with m.If(counter_reset): - sync += counter.eq(0) - with m.Elif(counter_ce): - sync += counter.eq(counter + 1) - - counter_done = Signal() - comb += counter_done.eq(counter == ratio-1) - - # Main FSM - with m.FSM() as fsm: - with m.State("IDLE"): - comb += counter_reset.eq(1) - sync += shift_reg.eq(0) - with m.If(master.stb & master.cyc): - with m.If(master.we): - m.next = "WRITE" - with m.Else(): - m.next = "READ" - - with m.State("WRITE"): - comb += write.eq(1) - comb += slave.we.eq(1) - comb += slave.cyc.eq(1) - with m.If(master.stb & master.cyc): - comb += slave.stb.eq(1) - with m.If(slave.ack): - comb += counter_ce.eq(1) - with m.If(counter_done): - comb += master.ack.eq(1) - m.next = "IDLE" - with m.Elif(~master.cyc): - m.next = "IDLE" - - with m.State("READ"): - comb += read.eq(1) - comb += slave.cyc.eq(1) - with m.If(master.stb & master.cyc): - comb += slave.stb.eq(1) - with m.If(slave.ack): - comb += counter_ce.eq(1) - with m.If(counter_done): - comb += master.ack.eq(1) - comb += master.dat_r.eq(cached_data) - m.next = "IDLE" - with m.Elif(~master.cyc): - m.next = "IDLE" - - # Address - with m.If(counter_done): - comb += slave.cti.eq(7) # indicate end of burst - with m.Else(): - comb += slave.cti.eq(2) - comb += slave.adr.eq(Cat(counter, master.adr)) - - # write Datapath - select fragments of data, depending on "counter" - with m.Switch(counter): - for i in range(ratio): - with m.Case(i): - # select fractions of dat_w and associated "sel" bits - comb += slave.sel.eq(master.sel[i*dw_to//8:(i+1)*dw_to//8]) - comb += slave.dat_w.eq(master.dat_w[i*dw_to:(i+1)*dw_to]) - - # read Datapath - uses cached_data and master.dat_r as a shift-register. - # by the time "counter" is done (counter_done) this is complete - comb += shift_reg.eq(Cat(cached_data[dw_to:], slave.dat_r)) - with m.If(read & counter_ce): - sync += cached_data.eq(shift_reg) - -