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:
Robert Jördens 2015-04-11 22:52:35 -06:00
parent bc1acef355
commit 6a0e97f161
2 changed files with 28 additions and 23 deletions

View File

@ -1,6 +1,6 @@
# Robert Jordens <jordens@gmail.com>, 2012-2015
from math import log2, sqrt
from math import log, sqrt
import logging
import struct
@ -95,7 +95,6 @@ class Channel:
del self.segments[:]
def new_segment(self):
# assert len(self.segments) < self.num_frames
segment = Segment()
self.segments.append(segment)
return segment
@ -186,25 +185,31 @@ class Pdq2:
s = self.channels[channel].segments[segment]
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):
self.clear_all()
for frame_data in program:
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(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.program_frame(frame_data)
self.write_all()

View File

@ -46,7 +46,7 @@ class TestPdq2(unittest.TestCase):
buf = self.test_cmd_program()
tb = Pdq2Sim(buf)
tb.ctrl_pads.trigger.reset = 0
tb.ctrl_pads.trigger.reset = 1
run_simulation(tb, ncycles=len(buf) + 250)
delays = 7, 10, 30
y = list(zip(*tb.outputs[len(buf) + 130:]))
@ -65,8 +65,8 @@ class TestPdq2(unittest.TestCase):
yij = yij*20./2**16
if yij > 10:
yij -= 20
self.assertAlmostEqual(yij, yij_ref, 2,
"foo t={}, c={}".format(i, j))
self.assertAlmostEqual(yij, yij_ref, 2, "disagreement at "
"t={}, c={}".format(i, j))
@unittest.skipUnless(pdq2_gateware, "no pdq2 gateware")
@unittest.skip("manual/visual test")