Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion misoc/cores/duc.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,11 @@ def __init__(self, width, constants):
###

# TODO: improve MCM
assert n <= 9
assert n <= 16
assert range(n) == constants

# manually generated multiplication for small numbers,
# if you use "x*y" Vivado will use a DSP48E1 instead
ctx = self.comb
if n > 0:
ctx += o[0].eq(0)
Expand All @@ -228,6 +230,20 @@ def __init__(self, width, constants):
ctx += o[7].eq((i << 3) - i)
if n > 8:
ctx += o[8].eq(i << 3)
if n > 9:
ctx += o[9].eq(i + (i << 3))
if n > 10:
ctx += o[10].eq(o[5] << 1)
if n > 11:
ctx += o[11].eq(i + (i << 3) + (i << 1))
if n > 12:
ctx += o[12].eq(o[6] << 1)
if n > 13:
ctx += o[13].eq(i + (i << 3) + (i << 2))
if n > 14:
ctx += o[14].eq(o[7] << 1)
if n > 15:
ctx += o[15].eq((i << 4) - i)


class PhasedAccu(Module):
Expand Down Expand Up @@ -267,6 +283,54 @@ def __init__(self, n, fwidth, pwidth):
for oi, z in zip(self.mcm.o, self.z)]
]

class PhasedAccuPipelined(Module):
"""Phase accumulator with multiple phased outputs.

Output data (across cycles and outputs) is such
that there is always one frequency word offset between successive
phase samples.

* Input frequency, phase offset, clear
* Output `n` phase samples per cycle
"""
def __init__(self, n, fwidth, pwidth):
self.f = Signal(fwidth)
self.p = Signal(pwidth)
self.clr = Signal(reset=1)
self.z = [Signal(pwidth, reset_less=True)
for _ in range(n)]

self.submodules.mcm = MCM(fwidth, range(n+1))
# reset by clr
qa = Signal(fwidth, reset_less=True)
qb = Signal(fwidth, reset_less=True)
clr_d = Signal(reset_less=True)

clr_d2 = Signal(reset_less=True)
mcm_o_reg = [Signal(fwidth, reset_less=True) for _ in range(n+1)]
mcm_o_d = [Signal(fwidth, reset_less=True) for _ in range(n)]
self.sync += [
# extra register layer to accomodate latency
[mcm_o_reg[i].eq(self.mcm.o[i]) for i in range(n+1)],
# Delay signals to match now increased mcm latency
clr_d.eq(self.clr),
clr_d2.eq(clr_d),
[mcm_o_d[i].eq(mcm_o_reg[i]) for i in range(n)],

qa.eq(qa + mcm_o_reg[n]),
self.mcm.i.eq(self.f),
If(clr_d | clr_d2,
qa.eq(0),
),
If(clr_d2,
self.mcm.i.eq(0),
),
qb.eq(qa + (self.p << (fwidth - pwidth))),

# Use delayed signals in the final phase calculation
[z.eq((qb + mcm_o_d[i])[fwidth - pwidth:])
for i, z in enumerate(self.z)]
]

class PhaseModulator(Module):
"""Complex phase modulator/shifter.
Expand Down
92 changes: 92 additions & 0 deletions misoc/test/test_duc.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def test_init(self):

def test_seq(self):
def gen():
# latency = 2
yield self.dut.clr.eq(0)
yield self.dut.p.eq(0x01)
yield
Expand Down Expand Up @@ -104,6 +105,97 @@ def gen():
self.assertEqual((yield self.dut.z[1]), 0xa1)
run_simulation(self.dut, gen())

class TestPhasedAccuNonLog(unittest.TestCase):
def setUp(self):
self.dut = duc.PhasedAccuPipelined(n=12, fwidth=32, pwidth=16)

def test_init(self):
self.assertEqual(len(self.dut.f), 32)
self.assertEqual(len(self.dut.p), 16)
self.assertEqual(len(self.dut.z), 12)
self.assertEqual(len(self.dut.z[0]), 16)

def test_seq(self):
def gen():
# latency = 4
n=12
yield self.dut.clr.eq(0)
yield self.dut.p.eq(0x01)
yield
yield
yield
yield
# check phase offset with f=0
self.assertEqual((yield self.dut.z[0]), 0x01)
self.assertEqual((yield self.dut.z[1]), 0x01)
yield self.dut.f.eq(0x10 << 16)
yield
yield
yield
yield
yield
# check first cycle f increments
for i in range(n):
expected_value = (i << 4) | 0x01
self.assertEqual((yield self.dut.z[i]), expected_value)
yield
# second cycle f increments
for i in range(n):
expected_value += 0x10
self.assertEqual((yield self.dut.z[i]), expected_value)
yield self.dut.clr.eq(1)
yield
for i in range(n):
expected_value += 0x10
self.assertEqual((yield self.dut.z[i]), expected_value)
yield
for i in range(n):
expected_value += 0x10
self.assertEqual((yield self.dut.z[i]), expected_value)
yield self.dut.clr.eq(0)
yield
for i in range(n):
expected_value += 0x10
yield
for i in range(n):
expected_value += 0x10
self.assertEqual((yield self.dut.z[i]), expected_value)
yield
# first clr cycle
for i in range(n):
expected_value = (i << 4) | 0x01
self.assertEqual((yield self.dut.z[i]), expected_value)
yield
# second clr cycle
for i in range(n):
expected_value = (i << 4) | 0x01
self.assertEqual((yield self.dut.z[i]), expected_value)
yield self.dut.f.eq(0x20 << 16)
yield
yield
yield
# first cycle after clr with old f
for i in range(n):
expected_value = (i << 4) | 0x01
self.assertEqual((yield self.dut.z[i]), expected_value)
yield
# second cycle with old f
for i in range(n):
expected_value += 0x10
self.assertEqual((yield self.dut.z[i]), expected_value)
yield
# cycle with one old and one new
expected_value += 0x10
for i in range(n):
self.assertEqual((yield self.dut.z[i]), expected_value)
expected_value += 0x20
yield
# cycle with only new increments
expected_value -= 0x20
for i in range(n):
expected_value += 0x20
self.assertEqual((yield self.dut.z[i]), expected_value)
run_simulation(self.dut, gen())

class TestMul(unittest.TestCase):
def setUp(self):
Expand Down