forked from M-Labs/artiq
pdq2: refactor program_frame(), cleanup test, stall correctly
Once the Sequencer ack's a line, the Parser starts preparing the next one. This includes jumping through the frame table if necessary. To stall the Parser while the Sequencer executes the last line of a frame and to ensure that the frame select lines can be set up and their sampling is synchronized to a trigger, we add a triggered stall line at the end of the frame. When that line is triggered the Parser jumps through the table and starts parsing the first line of the next frame. We let the duration of this last stall line be 10 cycles (200ns@50MHz) to be able to distinguish this sampling of the frame select lines from the triggering of the first line in the next frame. frame f parser n f 0 stb __---________---___ trigger ___----_______----_ ack ____-__________-___ sequencer n-1 n 0
This commit is contained in:
parent
bc1acef355
commit
6a0e97f161
|
@ -1,6 +1,6 @@
|
||||||
# Robert Jordens <jordens@gmail.com>, 2012-2015
|
# Robert Jordens <jordens@gmail.com>, 2012-2015
|
||||||
|
|
||||||
from math import log2, sqrt
|
from math import log, sqrt
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
|
@ -95,7 +95,6 @@ class Channel:
|
||||||
del self.segments[:]
|
del self.segments[:]
|
||||||
|
|
||||||
def new_segment(self):
|
def new_segment(self):
|
||||||
# assert len(self.segments) < self.num_frames
|
|
||||||
segment = Segment()
|
segment = Segment()
|
||||||
self.segments.append(segment)
|
self.segments.append(segment)
|
||||||
return segment
|
return segment
|
||||||
|
@ -186,25 +185,31 @@ class Pdq2:
|
||||||
s = self.channels[channel].segments[segment]
|
s = self.channels[channel].segments[segment]
|
||||||
self.write_mem(channel, s.data, s.adr)
|
self.write_mem(channel, s.data, s.adr)
|
||||||
|
|
||||||
|
def program_frame(self, frame_data):
|
||||||
|
segments = [c.new_segment() for c in self.channels]
|
||||||
|
for i, line in enumerate(frame_data): # segments are concatenated
|
||||||
|
dac_divider = line.get("dac_divider", 1)
|
||||||
|
shift = int(log(dac_divider, 2))
|
||||||
|
if 2**shift != dac_divider:
|
||||||
|
raise ValueError("only power-of-two dac_dividers supported")
|
||||||
|
duration = line["duration"]
|
||||||
|
trigger = line.get("trigger", False)
|
||||||
|
for segment, data in zip(segments, line["channel_data"]):
|
||||||
|
if len(data) != 1:
|
||||||
|
raise ValueError("only one target per channel and line "
|
||||||
|
"supported")
|
||||||
|
for target, target_data in data.items():
|
||||||
|
getattr(segment, target)(
|
||||||
|
shift=shift, duration=duration, trigger=trigger,
|
||||||
|
**target_data)
|
||||||
|
# append an empty line to stall the memory reader before jumping
|
||||||
|
# through the frame table
|
||||||
|
for segment in segments:
|
||||||
|
segment.line(typ=3, data=b"", trigger=True, duration=10)
|
||||||
|
return segments
|
||||||
|
|
||||||
def program(self, program):
|
def program(self, program):
|
||||||
self.clear_all()
|
self.clear_all()
|
||||||
for frame_data in program:
|
for frame_data in program:
|
||||||
segments = [c.new_segment() for c in self.channels]
|
self.program_frame(frame_data)
|
||||||
for i, line in enumerate(frame_data): # segments are concatenated
|
|
||||||
dac_divider = line.get("dac_divider", 1)
|
|
||||||
shift = int(log2(dac_divider))
|
|
||||||
assert 2**shift == dac_divider
|
|
||||||
duration = line["duration"]
|
|
||||||
trigger = line.get("trigger", False)
|
|
||||||
if i == 0:
|
|
||||||
assert trigger
|
|
||||||
trigger = False # use wait on the last line
|
|
||||||
eof = i == len(frame_data) - 1
|
|
||||||
for segment, data in zip(segments, line.get("channel_data")):
|
|
||||||
assert len(data) == 1
|
|
||||||
for target, target_data in data.items():
|
|
||||||
getattr(segment, target)(
|
|
||||||
shift=shift, duration=duration,
|
|
||||||
trigger=trigger, wait=eof, jump=eof,
|
|
||||||
**target_data)
|
|
||||||
self.write_all()
|
self.write_all()
|
||||||
|
|
|
@ -46,7 +46,7 @@ class TestPdq2(unittest.TestCase):
|
||||||
|
|
||||||
buf = self.test_cmd_program()
|
buf = self.test_cmd_program()
|
||||||
tb = Pdq2Sim(buf)
|
tb = Pdq2Sim(buf)
|
||||||
tb.ctrl_pads.trigger.reset = 0
|
tb.ctrl_pads.trigger.reset = 1
|
||||||
run_simulation(tb, ncycles=len(buf) + 250)
|
run_simulation(tb, ncycles=len(buf) + 250)
|
||||||
delays = 7, 10, 30
|
delays = 7, 10, 30
|
||||||
y = list(zip(*tb.outputs[len(buf) + 130:]))
|
y = list(zip(*tb.outputs[len(buf) + 130:]))
|
||||||
|
@ -65,8 +65,8 @@ class TestPdq2(unittest.TestCase):
|
||||||
yij = yij*20./2**16
|
yij = yij*20./2**16
|
||||||
if yij > 10:
|
if yij > 10:
|
||||||
yij -= 20
|
yij -= 20
|
||||||
self.assertAlmostEqual(yij, yij_ref, 2,
|
self.assertAlmostEqual(yij, yij_ref, 2, "disagreement at "
|
||||||
"foo t={}, c={}".format(i, j))
|
"t={}, c={}".format(i, j))
|
||||||
|
|
||||||
@unittest.skipUnless(pdq2_gateware, "no pdq2 gateware")
|
@unittest.skipUnless(pdq2_gateware, "no pdq2 gateware")
|
||||||
@unittest.skip("manual/visual test")
|
@unittest.skip("manual/visual test")
|
||||||
|
|
Loading…
Reference in New Issue