# Copyright (c) 2019 Antti Lukats <antti.lukats@gmail.com>
# Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr>
# Copyright (c) 2021 gatecat <gatecat@ds0.me> [nmigen-soc port]
-# Copyright (C) 2021 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
+# Copyright (C) 2022 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
#
# Code from Lukats, Kermarrec and gatecat is Licensed BSD-2-Clause
#
# 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
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)
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):
--- /dev/null
+# Test of HyperRAM class
+#
+# Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr>
+# Copyright (c) 2021 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
+# 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()