From: Luke Kenneth Casson Leighton Date: Wed, 16 Mar 2022 20:20:50 +0000 (+0000) Subject: update HyperRAM module and add unit test X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=78df077ec1b48f851688646a187e168e789bca20;p=lambdasoc.git update HyperRAM module and add unit test --- diff --git a/lambdasoc/periph/hyperram.py b/lambdasoc/periph/hyperram.py index 4e056d5..8220d7b 100644 --- a/lambdasoc/periph/hyperram.py +++ b/lambdasoc/periph/hyperram.py @@ -3,7 +3,7 @@ # Copyright (c) 2019 Antti Lukats # Copyright (c) 2019 Florent Kermarrec # Copyright (c) 2021 gatecat [nmigen-soc port] -# Copyright (C) 2021 Luke Kenneth Casson Leighton +# Copyright (C) 2022 Luke Kenneth Casson Leighton # # Code from Lukats, Kermarrec and gatecat is Licensed BSD-2-Clause # @@ -11,7 +11,7 @@ # under EU Grants 871528 and 957073, and Licensed under the LGPLv3+ License -from nmigen import (Elaboratable, Module, Signal, Record, Cat) +from nmigen import (Elaboratable, Module, Signal, Record, Cat, Const) from nmigen.cli import rtlil from nmigen_soc import wishbone @@ -98,10 +98,11 @@ class HyperRAM(Peripheral, Elaboratable): This core favors portability and ease of use over performance. """ - def __init__(self, *, io, phy_kls): + def __init__(self, *, io, phy_kls, latency=6): super().__init__() self.io = io self.phy = phy_kls(io) + self.latency = latency self.bus = wishbone.Interface(addr_width=21, data_width=32, granularity=8) mmap = MemoryMap(addr_width=23, data_width=8) @@ -113,76 +114,159 @@ class HyperRAM(Peripheral, Elaboratable): def elaborate(self, platform): m = Module() m.submodules.phy = self.phy + bus = self.bus + comb, sync = m.d.comb, m.d.sync clk = self.phy.clk clk_phase = Signal(2) cs = self.phy.cs ca = Signal(48) + ca_active = Signal() sr = Signal(48) + sr_new = Signal(48) dq_o = self.phy.dq_o dq_i = self.phy.dq_i dq_oe = self.phy.dq_oe + dw = len(dq_o) # data width rwds_o = self.phy.rwds_o rwds_oe = self.phy.rwds_oe # Clock Generation (sys_clk/4) ----------------------------------- - m.d.sync += clk_phase.eq(clk_phase + 1) + sync += clk_phase.eq(clk_phase + 1) with m.Switch(clk_phase): with m.Case(1): - m.d.sync += clk.eq(cs) + sync += clk.eq(cs) with m.Case(3): - m.d.sync += clk.eq(0) + sync += clk.eq(0) # Data Shift Register (for write and read) ------------------------ - dqi = Signal(8) - m.d.sync += dqi.eq(dq_i) # Sample on 90° and 270° - with m.Switch(clk_phase): - with m.Case(0, 2): - m.d.sync += sr.eq(Cat(dqi, sr[:-8])) - - m.d.comb += [ - self.bus.dat_r.eq(sr), # To Wisbone - dq_o.eq(sr[-8:]), # To HyperRAM - ] + dqi = Signal(dw) + sync += dqi.eq(dq_i) # Sample on 90° and 270° + with m.If(ca_active): + comb += sr_new.eq(Cat(dqi[:8], sr[:-dw])) + with m.Else(): + comb += sr_new.eq(Cat(dqi, sr[:-8])) + with m.If(~clk_phase[0]): + sync += sr.eq(sr_new) # Shift on 0° and 180° + + # Data shift-out register ---------------------------------------- + comb += self.bus.dat_r.eq(sr_new), # To Wisbone + with m.If(dq_oe): + comb += dq_o.eq(sr[-dw:]), # To HyperRAM + with m.If(dq_oe & ca_active): + comb += dq_o.eq(sr[-8:]), # To HyperRAM, Only 8-bit during CMD/Addr. # Command generation ---------------------------------------------- - m.d.comb += [ + ashift = {8:1, 16:0}[dw] + la = 3-ashift + comb += [ ca[47].eq(~self.bus.we), # R/W# ca[45].eq(1), # Burst Type (Linear) - ca[16:35].eq(self.bus.adr[2:21]), # Row & Upper Column Address + ca[16:45].eq(self.bus.adr[la:]), # Row & Upper Column Address ca[1:3].eq(self.bus.adr[0:2]), # Lower Column Address - ca[0].eq(0), # Lower Column Address + ca[ashift:3].eq(bus.adr), # Lower Column Address ] + # Latency count starts from the middle of the command (thus the -4). + # In fixed latency mode (default), latency is 2 x Latency count. + # We have 4 x sys_clk per RAM clock: + latency_cycles = (self.latency * 2 * 4) - 4 + + # Bus Latch ---------------------------------------------------- + bus_adr = Signal(32) + bus_we = Signal() + bus_sel = Signal(4) + bus_latch = Signal() + with m.If(bus_latch): + with m.If(bus.we): + sync += sr.eq(Cat(Const(0, 16), bus.dat_w)) + sync += [ bus_we.eq(bus.we), + bus_sel.eq(bus.sel), + bus_adr.eq(bus.adr) + ] + + + # Sequencer ------------------------------------------------------- - dt_seq = [ - # DT, Action - (3, []), - (12, [cs.eq(1), dq_oe.eq(1), sr.eq(ca)]), # Command: 6 clk - (44, [dq_oe.eq(0)]), # Latency(dflt): 2*6 clk - (2, [dq_oe.eq(self.bus.we), # Wr/Rd data byte: 2 clk - sr[:16].eq(0), - sr[16:].eq(self.bus.dat_w), - rwds_oe.eq(self.bus.we), - rwds_o.eq(~self.bus.sel[3])]), - (2, [rwds_o.eq(~self.bus.sel[2])]), # Wr/Rd data byte: 2 clk - (2, [rwds_o.eq(~self.bus.sel[1])]), # Wr/Rd data byte: 2 clk - (2, [rwds_o.eq(~self.bus.sel[0])]), # Wr/Rd data byte: 2 clk - (2, [cs.eq(0), rwds_oe.eq(0), dq_oe.eq(0)]), - (1, [self.bus.ack.eq(1)]), - (1, [self.bus.ack.eq(0)]), - (0, []), - ] - # Convert delta-time sequencer to time sequencer - t_seq = [] - t_seq_start = (clk_phase == 1) - t = 0 - for dt, a in dt_seq: - t_seq.append((t, a)) - t += dt - timeline(m, self.bus.cyc & self.bus.stb & t_seq_start, t_seq) + cycles = Signal(8) + first = Signal() + count_inc = Signal() + dbg_cyc = Signal(8) + + # when not idle run a cycles counter + with m.If(count_inc): + sync += dbg_cyc.eq(dbg_cyc+1) + with m.Else(): + sync += dbg_cyc.eq(0) + + # Main FSM + with m.FSM() as fsm: + comb += count_inc.eq(~fsm.ongoing("IDLE")) + with m.State("IDLE"): + sync += first.eq(1) + with m.If(bus.cyc & bus.stb & (clk_phase == 0)): + sync += sr.eq(ca) + m.next = "SEND-COMMAND-ADDRESS" + sync += cycles.eq(0) + + with m.State("SEND-COMMAND-ADDRESS"): + sync += cycles.eq(cycles+1) + comb += cs.eq(1) # Set CSn. + comb += ca_active.eq(1) # Send Command on DQ. + comb += dq_oe.eq(1), # Wait for 6*2 cycles... + with m.If(cycles == (6*2 - 1)): + m.next = "WAIT-LATENCY" + sync += cycles.eq(0) + + with m.State("WAIT-LATENCY"): + sync += cycles.eq(cycles+1) + comb += cs.eq(1) # Set CSn. + # Wait for Latency cycles... + with m.If(cycles == (latency_cycles - 1)): + comb += bus_latch.eq(1) # Latch Bus. + # Early Write Ack (to allow bursting). + comb += bus.ack.eq(bus.we) + m.next = "READ-WRITE-DATA0" + sync += cycles.eq(0) + + states = {8:4, 16:2}[dw] + for n in range(states): + with m.State("READ-WRITE-DATA%d" % n): + sync += cycles.eq(cycles+1) + comb += cs.eq(1), # Set CSn. + # Send Data on DQ/RWDS (for write). + with m.If(bus_we): + comb += dq_oe.eq(1) + comb += rwds_oe.eq(1) + for i in range(dw//8): + seli = ~bus_sel[4-1-n*dw//8-i] + comb += rwds_o[dw//8-1-i].eq(seli) + # Wait for 2 cycles (since HyperRAM's Clk = sys_clk/4). + with m.If(cycles == (2 - 1)): + # Set next default state (with rollover for bursts). + m.next = "READ-WRITE-DATA%d" % ((n + 1) % states) + sync += cycles.eq(0) + # On last state, see if we can continue the burst + # or if we should end it. + with m.If(n == (states - 1)): + sync += first.eq(0) + # Continue burst when consecutive access ready. + with m.If(bus.stb & bus.cyc & + (bus.we == bus_we) & + (bus.adr == (bus_adr + 1))): + comb += bus_latch.eq(1), # Latch Bus. + # Early Write Ack (to allow bursting). + comb += bus.ack.eq(bus.we) + # Else end the burst. + with m.Elif(bus_we | ~first): + m.next = "IDLE" + sync += cycles.eq(0) + # Read Ack (when dat_r ready). + with m.If((n == 0) & ~first): + comb += bus.ack.eq(~bus_we) + return m def ports(self): diff --git a/lambdasoc/test/test_hyperbus.py b/lambdasoc/test/test_hyperbus.py new file mode 100644 index 0000000..2191e63 --- /dev/null +++ b/lambdasoc/test/test_hyperbus.py @@ -0,0 +1,220 @@ +# Test of HyperRAM class +# +# Copyright (c) 2019 Florent Kermarrec +# Copyright (c) 2021 Luke Kenneth Casson Leighton +# Based on code from Kermarrec, Licensed BSD-2-Clause +# +# Modifications for the Libre-SOC Project funded by NLnet and NGI POINTER +# under EU Grants 871528 and 957073, under the LGPLv3+ License + +import unittest + +from nmigen import (Record, Module, Signal, Elaboratable) +from nmigen.compat.sim import run_simulation + +from lambdasoc.periph.hyperram import HyperRAM + +def c2bool(c): + return {"-": 1, "_": 0}[c] + + +def wb_write(bus, addr, data, sel): + yield bus.adr.eq(addr) + yield bus.dat_w.eq(data) + yield bus.sel.eq(sel) + yield bus.we.eq(1) + yield bus.cyc.eq(1) + yield bus.stb.eq(1) + yield + while not (yield bus.ack): + yield + yield bus.cyc.eq(0) + yield bus.stb.eq(0) + + +def wb_read(bus, addr, sel): + yield bus.adr.eq(addr) + yield bus.sel.eq(sel) + yield bus.cyc.eq(1) + yield bus.stb.eq(1) + yield + while not (yield bus.ack): + yield + yield bus.cyc.eq(0) + yield bus.stb.eq(0) + + return (yield bus.dat_r) + + +class Pads: pass + + +class HyperRamPads: + def __init__(self, dw=8): + self.clk = Signal() + self.cs_n = Signal() + self.dq = Record([("oe", 1), ("o", dw), ("i", dw)]) + self.rwds = Record([("oe", 1), ("o", dw//8), ("i", dw//8)]) + + +class TestHyperRAMPHY(Elaboratable): + def __init__(self, pads): + self.pads = pads + self.clk = pads.clk + self.cs = Signal() + self.dq_o = pads.dq.o + self.dq_i = pads.dq.i + self.dq_oe = pads.dq.oe + self.rwds_o = pads.rwds.o + self.rwds_oe = Signal() + + def elaborate(self, platform): + m = Module() + m.d.comb += self.pads.cs_n.eq(~self.cs) + m.d.comb += self.pads.rwds.oe.eq(self.rwds_oe) + return m + + +class TestHyperBusWrite(unittest.TestCase): + + def test_hyperram_write(self): + def fpga_gen(dut): + yield from wb_write(dut.bus, 0x1234, 0xdeadbeef, sel=0b1001) + yield + + def hyperram_gen(dut): + clk = "___--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--_______" + cs_n = "--________________________________________________________________------" + dq_oe = "__------------____________________________________________--------______" + dq_o = "002000048d000000000000000000000000000000000000000000000000deadbeef000000" + rwds_oe = "__________________________________________________________--------______" + rwds_o = "____________________________________________________________----________" + + #clk = "___--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--_______" + #cs_n = "--________________________________________________________________------" + #dq_oe = "__------------____________________________________________--------______" + #dq_o = "002000048d000000000000000000000000000000000000000000000000deadbeef000000" + #rwds_oe = "__________________________________________________________--------______" + #rwds_o = "________________________________________________________________________" + for i in range(3): + yield + if False: # useful for printing out expected vs results + for i in range(len(clk)): + print ("i", i) + print ("clk", c2bool(clk[i]), (yield dut.phy.pads.clk)) + print ("cs_n", c2bool(cs_n[i]), (yield dut.phy.pads.cs_n)) + print ("dq_oe", c2bool(dq_oe[i]), (yield dut.phy.pads.dq.oe)) + print ("dq_o", hex(int(dq_o[2*(i//2):2*(i//2)+2], 16)), + hex((yield dut.phy.pads.dq.o))) + print ("rwds_oe", c2bool(rwds_oe[i]), + (yield dut.phy.pads.rwds.oe)) + print ("rwds_o", c2bool(rwds_o[i]), + (yield dut.phy.pads.rwds.o)) + yield + for i in range(len(clk)): + self.assertEqual(c2bool(clk[i]), (yield dut.phy.pads.clk)) + self.assertEqual(c2bool(cs_n[i]), (yield dut.phy.pads.cs_n)) + self.assertEqual(c2bool(dq_oe[i]), (yield dut.phy.pads.dq.oe)) + self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), + (yield dut.phy.pads.dq.o)) + self.assertEqual(c2bool(rwds_oe[i]), + (yield dut.phy.pads.rwds.oe)) + self.assertEqual(c2bool(rwds_o[i]), + (yield dut.phy.pads.rwds.o)) + yield + + dut = HyperRAM(io=HyperRamPads(), phy_kls=TestHyperRAMPHY) + run_simulation(dut, [fpga_gen(dut), hyperram_gen(dut)], + vcd_name="sim.vcd") + + def test_hyperram_read(self): + def fpga_gen(dut): + dat = yield from wb_read(dut.bus, 0x1234, 0b1111) + self.assertEqual(dat, 0xdeadbeef) + dat = yield from wb_read(dut.bus, 0x1235, 0b1111) + self.assertEqual(dat, 0xcafefade) + + def hyperram_gen(dut): + clk = "___--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--_" + cs_n = "--________________________________________________________________________________" + dq_oe = "__------------____________________________________________________________________" + dq_o = "00a000048d000000000000000000000000000000000000000000000000000000000000000000000000" + dq_i = "0000000000000000000000000000000000000000000000000000000000deadbeefcafefade00000000" + rwds_oe = "__________________________________________________________________________________" + + for i in range(3): + print ("prep", i) + yield + for i in range(len(clk)): + print ("i", i) + yield dut.phy.pads.dq.i.eq(int(dq_i[2*(i//2):2*(i//2)+2], 16)) + print ("clk", c2bool(clk[i]), (yield dut.phy.pads.clk)) + print ("cs_n", c2bool(cs_n[i]), (yield dut.phy.pads.cs_n)) + yield + continue + self.assertEqual(c2bool(clk[i]), (yield dut.phy.pads.clk)) + self.assertEqual(c2bool(cs_n[i]), (yield dut.phy.pads.cs_n)) + self.assertEqual(c2bool(dq_oe[i]), (yield dut.phy.pads.dq.oe)) + self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), + (yield dut.phy.pads.dq.o)) + self.assertEqual(c2bool(rwds_oe[i]), + (yield dut.phy.pads.rwds.oe)) + yield + + dut = HyperRAM(io=HyperRamPads(), phy_kls=TestHyperRAMPHY) + run_simulation(dut, [fpga_gen(dut), hyperram_gen(dut)], + vcd_name="rd_sim.vcd") + +class TestHyperBusRead(unittest.TestCase): + def test_hyperram_read(self): + def fpga_gen(dut): + dat = yield from wb_read(dut.bus, 0x1234, 0b1111) + self.assertEqual(dat, 0xdeadbeef) + dat = yield from wb_read(dut.bus, 0x1235, 0b1111) + self.assertEqual(dat, 0xcafefade) + yield + yield + yield + yield + yield + + def hyperram_gen(dut): + clk = "___--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--_" + cs_n = "--________________________________________________________________________________" + dq_oe = "__------------____________________________________________________________________" + dq_o = "00a000048d000000000000000000000000000000000000000000000000000000000000000000000000" + dq_i = "0000000000000000000000000000000000000000000000000000000000deadbeefcafefade00000000" + rwds_oe = "__________________________________________________________________________________" + + for i in range(3): + print ("prep", i) + yield + for i in range(len(clk)): + print ("i", i) + yield dut.phy.pads.dq.i.eq(int(dq_i[2*(i//2):2*(i//2)+2], 16)) + print ("clk", c2bool(clk[i]), (yield dut.phy.pads.clk)) + print ("cs_n", c2bool(cs_n[i]), (yield dut.phy.pads.cs_n)) + print ("dq_oe", c2bool(dq_oe[i]), (yield dut.phy.pads.dq.oe)) + print ("dq_o", hex(int(dq_o[2*(i//2):2*(i//2)+2], 16)), + hex((yield dut.phy.pads.dq.o))) + print ("rwds_oe", c2bool(rwds_oe[i]), + (yield dut.phy.pads.rwds.oe)) + self.assertEqual(c2bool(clk[i]), (yield dut.phy.pads.clk)) + self.assertEqual(c2bool(cs_n[i]), (yield dut.phy.pads.cs_n)) + self.assertEqual(c2bool(dq_oe[i]), (yield dut.phy.pads.dq.oe)) + self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), + (yield dut.phy.pads.dq.o)) + self.assertEqual(c2bool(rwds_oe[i]), + (yield dut.phy.pads.rwds.oe)) + yield + + dut = HyperRAM(io=HyperRamPads(), phy_kls=TestHyperRAMPHY) + run_simulation(dut, [fpga_gen(dut), hyperram_gen(dut)], + vcd_name="rd_sim.vcd") + +if __name__ == '__main__': + unittest.main() + #t = TestHyperBusRead() + #t.test_hyperram_read() + #t = TestHyperBusWrite() + #t.test_hyperram_write()