forked from M-Labs/artiq
wrpll: document DDMTD collector and fix unwrapping
This commit is contained in:
parent
7c2519c912
commit
ebb7ccbfd1
|
@ -130,61 +130,180 @@ class DDMTD(Module, AutoCSR):
|
||||||
|
|
||||||
|
|
||||||
class Collector(Module):
|
class Collector(Module):
|
||||||
def __init__(self, N):
|
""" Generates loop filter inputs from DDMTD outputs.
|
||||||
self.tag_helper = Signal((N, True))
|
|
||||||
self.tag_helper_update = Signal()
|
|
||||||
self.tag_main = Signal((N, True))
|
|
||||||
self.tag_main_update = Signal()
|
|
||||||
|
|
||||||
self.output = Signal((N + 1, True))
|
When the WR PLL is locked, the following ideally (no noise etc) obtain:
|
||||||
|
- f_main = f_ref
|
||||||
|
- f_helper = f_ref * (2^N-1) / 2^N
|
||||||
|
- f_beat = f_ref - f_helper = f_ref / 2^N (cycle time is: dt=1/f_beat)
|
||||||
|
- the reference and main DCXO tags are equal each cycle
|
||||||
|
- the reference and main DCXO tags decrease by 1 each cycle (the tag
|
||||||
|
difference is f_helper*dt = f_helper/f_beat = (2^N-1) so we are 1 tag
|
||||||
|
away from a complete wrap around of the N-bit DDMTD counter)
|
||||||
|
|
||||||
|
Since the main and reference tags cycle through all possible values when
|
||||||
|
locked, we need to unwrap the collector outputs to avoid glitches
|
||||||
|
(particularly noise around transitions). Currently we do this in hardware,
|
||||||
|
but we should consider extending the processor to allow us to do it
|
||||||
|
inside the filters at a later stage (at which point, the collector
|
||||||
|
essentially becomes a the trigger for the loop filters).
|
||||||
|
|
||||||
|
The input to the main DCXO lock loop filter is the difference between the
|
||||||
|
reference and main tags after phase unwrapping.
|
||||||
|
|
||||||
|
The input to the helper DCXO lock loop filter is the difference between the
|
||||||
|
current reference tag and the previous reference tag plus 1, after
|
||||||
|
phase unwrapping.
|
||||||
|
"""
|
||||||
|
def __init__(self, N):
|
||||||
|
self.ref_stb = Signal()
|
||||||
|
self.main_stb = Signal()
|
||||||
|
self.tag_ref = Signal(N)
|
||||||
|
self.tag_main = Signal(N)
|
||||||
|
|
||||||
|
self.out_stb = Signal()
|
||||||
|
self.out_main = Signal((N+2, True))
|
||||||
|
self.out_helper = Signal((N+2, True))
|
||||||
|
self.out_tag_ref = Signal(N)
|
||||||
|
self.out_tag_main = Signal(N)
|
||||||
|
|
||||||
|
tag_ref_r = Signal(N)
|
||||||
|
tag_main_r = Signal(N)
|
||||||
|
main_tag_diff = Signal((N+2, True))
|
||||||
|
helper_tag_diff = Signal((N+2, True))
|
||||||
|
|
||||||
# # #
|
# # #
|
||||||
|
|
||||||
fsm = FSM(reset_state="IDLE")
|
fsm = FSM(reset_state="IDLE")
|
||||||
self.submodules += fsm
|
self.submodules += fsm
|
||||||
|
|
||||||
tag_collector = Signal((N + 1, True))
|
|
||||||
fsm.act("IDLE",
|
fsm.act("IDLE",
|
||||||
If(self.tag_main_update & self.tag_helper_update,
|
NextValue(self.out_stb, 0),
|
||||||
NextValue(tag_collector, 0),
|
If(self.ref_stb & self.main_stb,
|
||||||
NextState("UPDATE")
|
NextValue(tag_ref_r, self.tag_ref),
|
||||||
).Elif(self.tag_main_update,
|
NextValue(tag_main_r, self.tag_main),
|
||||||
NextValue(tag_collector, self.tag_main),
|
NextState("DIFF")
|
||||||
NextState("WAITHELPER")
|
).Elif(self.ref_stb,
|
||||||
).Elif(self.tag_helper_update,
|
NextValue(tag_ref_r, self.tag_ref),
|
||||||
NextValue(tag_collector, -self.tag_helper),
|
|
||||||
NextState("WAITMAIN")
|
NextState("WAITMAIN")
|
||||||
|
).Elif(self.main_stb,
|
||||||
|
NextValue(tag_main_r, self.tag_main),
|
||||||
|
NextState("WAITREF")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
fsm.act("WAITHELPER",
|
fsm.act("WAITREF",
|
||||||
If(self.tag_helper_update,
|
If(self.ref_stb,
|
||||||
NextValue(tag_collector, tag_collector - self.tag_helper),
|
NextValue(tag_ref_r, self.tag_ref),
|
||||||
NextState("LEADCHECK")
|
NextState("DIFF")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
fsm.act("WAITMAIN",
|
fsm.act("WAITMAIN",
|
||||||
If(self.tag_main_update,
|
If(self.main_stb,
|
||||||
NextValue(tag_collector, tag_collector + self.tag_main),
|
NextValue(tag_main_r, self.tag_main),
|
||||||
NextState("LAGCHECK")
|
NextState("DIFF")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# To compensate DDMTD counter roll-over when main is ahead of roll-over
|
fsm.act("DIFF",
|
||||||
# and helper is after roll-over
|
NextValue(main_tag_diff, tag_main_r - tag_ref_r),
|
||||||
fsm.act("LEADCHECK",
|
NextValue(helper_tag_diff, tag_ref_r - self.out_tag_ref + 1),
|
||||||
If(tag_collector > 0,
|
NextState("UNWRAP")
|
||||||
NextValue(tag_collector, tag_collector - (2**N - 1))
|
)
|
||||||
|
fsm.act("UNWRAP",
|
||||||
|
If(main_tag_diff - self.out_main > 2**(N-1),
|
||||||
|
NextValue(main_tag_diff, main_tag_diff - 2**N)
|
||||||
|
).Elif(self.out_main - main_tag_diff > 2**(N-1),
|
||||||
|
NextValue(main_tag_diff, main_tag_diff + 2**N)
|
||||||
),
|
),
|
||||||
NextState("UPDATE")
|
|
||||||
)
|
If(helper_tag_diff - self.out_helper > 2**(N-1),
|
||||||
# To compensate DDMTD counter roll-over when helper is ahead of roll-over
|
NextValue(helper_tag_diff, helper_tag_diff - 2**N)
|
||||||
# and main is after roll-over
|
).Elif(self.out_helper - helper_tag_diff > 2**(N-1),
|
||||||
fsm.act("LAGCHECK",
|
NextValue(helper_tag_diff, helper_tag_diff + 2**N)
|
||||||
If(tag_collector < 0,
|
|
||||||
NextValue(tag_collector, tag_collector + (2**N - 1))
|
|
||||||
),
|
),
|
||||||
NextState("UPDATE")
|
|
||||||
|
NextState("OUTPUT")
|
||||||
)
|
)
|
||||||
fsm.act("UPDATE",
|
fsm.act("OUTPUT",
|
||||||
NextValue(self.output, tag_collector),
|
NextValue(self.out_tag_ref, tag_ref_r),
|
||||||
|
NextValue(self.out_tag_main, tag_main_r),
|
||||||
|
NextValue(self.out_main, main_tag_diff),
|
||||||
|
NextValue(self.out_helper, helper_tag_diff),
|
||||||
|
NextValue(self.out_stb, 1),
|
||||||
NextState("IDLE")
|
NextState("IDLE")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_collector_main():
|
||||||
|
|
||||||
|
N = 2
|
||||||
|
collector = Collector(N=N)
|
||||||
|
# check collector phase unwrapping
|
||||||
|
tags = [(0, 0, 0),
|
||||||
|
(0, 1, 1),
|
||||||
|
(2, 1, -1),
|
||||||
|
(3, 1, -2),
|
||||||
|
(0, 1, -3),
|
||||||
|
(1, 1, -4),
|
||||||
|
(2, 1, -5),
|
||||||
|
(3, 1, -6),
|
||||||
|
(3, 3, -4),
|
||||||
|
(0, 0, -4),
|
||||||
|
(0, 1, -3),
|
||||||
|
(0, 2, -2),
|
||||||
|
(0, 3, -1),
|
||||||
|
(0, 0, 0)]
|
||||||
|
for i in range(10):
|
||||||
|
tags.append((i % (2**N), (i+1) % (2**N), 1))
|
||||||
|
|
||||||
|
def generator():
|
||||||
|
for tag_ref, tag_main, out in tags:
|
||||||
|
yield collector.tag_ref.eq(tag_ref)
|
||||||
|
yield collector.tag_main.eq(tag_main)
|
||||||
|
yield collector.main_stb.eq(1)
|
||||||
|
yield collector.ref_stb.eq(1)
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
yield collector.main_stb.eq(0)
|
||||||
|
yield collector.ref_stb.eq(0)
|
||||||
|
|
||||||
|
while not (yield collector.out_stb):
|
||||||
|
yield
|
||||||
|
|
||||||
|
out_main = yield collector.out_main
|
||||||
|
assert out_main == out
|
||||||
|
|
||||||
|
run_simulation(collector, generator())
|
||||||
|
|
||||||
|
|
||||||
|
def test_collector_helper():
|
||||||
|
|
||||||
|
N = 3
|
||||||
|
collector = Collector(N=N)
|
||||||
|
# check collector phase unwrapping
|
||||||
|
tags = [((2**N - 1 - tag) % (2**N), 0) for tag in range(20)]
|
||||||
|
tags += [((tags[-1][0] + 1 + tag) % (2**N), 2) for tag in range(20)]
|
||||||
|
tags += [((tags[-1][0] - 2 - 2*tag) % (2**N), -1) for tag in range(20)]
|
||||||
|
|
||||||
|
def generator():
|
||||||
|
for tag_ref, out in tags:
|
||||||
|
yield collector.tag_ref.eq(tag_ref)
|
||||||
|
yield collector.main_stb.eq(1)
|
||||||
|
yield collector.ref_stb.eq(1)
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
yield collector.main_stb.eq(0)
|
||||||
|
yield collector.ref_stb.eq(0)
|
||||||
|
|
||||||
|
while not (yield collector.out_stb):
|
||||||
|
yield
|
||||||
|
|
||||||
|
out_helper = yield collector.out_helper
|
||||||
|
assert out_helper == out
|
||||||
|
|
||||||
|
run_simulation(collector, generator())
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_collector_main()
|
||||||
|
test_collector_helper()
|
||||||
|
|
Loading…
Reference in New Issue