diff --git a/pulse_lib/keysight/qs_sequence.py b/pulse_lib/keysight/qs_sequence.py
index fd8cda99370ff02a7951fa9956768189d137cb7e..64d09c89e9672ed8bf19b1536691db50f171ad57 100644
--- a/pulse_lib/keysight/qs_sequence.py
+++ b/pulse_lib/keysight/qs_sequence.py
@@ -1,10 +1,10 @@
 from dataclasses import dataclass, field
 from typing import Union, Optional, List
-
+import math
 import numpy as np
 
 def iround(value):
-    return int(value + 0.5)
+    return math.floor(value + 0.5)
 
 @dataclass
 class Waveform:
@@ -18,6 +18,22 @@ class Waveform:
     offset: int = 0
     restore_frequency: bool = True
 
+    @property
+    def extra_samples(self):
+        # post_phase and pm_envelope add 2 samples, but last sample is restore of NCO
+        # frequency and can be overwritten by next pulse without consequences.
+        if self.postphase != 0:
+            return 1
+        try:
+            # if pm_envelope is not an array, then this obviously fails.
+            return 1 if self.pm_envelope[-1] != 0.0 else 0
+        except:
+            return 0
+
+    @property
+    def instruction_duration(self):
+        return self.offset + self.duration + self.extra_samples
+
     def __eq__(self, other):
         return (self.amplitude == other.amplitude
                 and np.all(self.am_envelope == other.am_envelope)
@@ -40,6 +56,34 @@ class SequenceConditionalEntry:
     cr: int = 0
     waveform_indices: List[int] = field(default_factory=list)
 
+@dataclass
+class Instruction:
+    t_start: int
+    ''' start time of instruction. Multiple on 5 ns. '''
+    waveform: Optional[Waveform] = None
+    phase_shift: Optional[float] = None
+
+    @property
+    def t_pulse_end(self):
+        ''' end time of instruction, exclusive. Multiple on 5 ns. '''
+        if self.waveform:
+            wvf = self.waveform
+            return self.t_start + wvf.duration + wvf.offset
+        else:
+            return self.t_start
+
+    @property
+    def t_instruction_end(self):
+        ''' end time of instruction, exclusive. Multiple on 5 ns. '''
+        if self.waveform:
+            wvf = self.waveform
+            duration = wvf.duration + wvf.offset + wvf.extra_samples
+            duration = (duration+4) // 5 * 5
+        else:
+            duration = 5
+        return self.t_start + duration
+
+
 @dataclass
 class DigitizerSequenceEntry:
     time_after: float = 0
@@ -50,127 +94,177 @@ class DigitizerSequenceEntry:
     pxi_trigger: Optional[int] = None
     n_cycles : int = 1
 
-@dataclass
-class PhaseShift:
-    t: float
-    phase_shift: float
-
-# TODO:
-#  instruction is 'raw' waveform like segment
-#  flush segment when t >= t_end (aligned)
 
 class IQSequenceBuilder:
     def __init__(self, name, t_start, lo_freq):
         self.name = name
         self.time = iround(t_start)
-        self.lo_freq = lo_freq
         self.end_pulse = self.time
-        self.last_instruction = None
-        self._pending_phase_shift = None
+        self.lo_freq = lo_freq
+        self.pending_instruction = None
         self.sequence = []
         self.waveforms = []
 
+    def shift_phase(self, t, phase_shift):
+        if abs(phase_shift) < 2*np.pi/2**18:
+            # phase shift is too small for hardware
+            return
+        self._add_phase_shift(t, phase_shift)
+
     def pulse(self, t_pulse, iq_pulse):
         # TODO: split long pulses in start + stop pulse (Rabi)
-        # TODO: Frequency chirp with prescaler: pass as FM i.s.o. PM, or Chirp?
-        self._flush_phase_shifts(t_pulse)
-        if self._pending_phase_shift is not None:
-            prephase = self._pending_phase_shift.phase_shift
-            self._pending_phase_shift = None
-        else:
-            prephase = 0
-
-        offset = self._get_wvf_offset(t_pulse)
-        waveform_index, duration = self._render_waveform(iq_pulse, offset, prephase=prephase)
-        t_end = t_pulse + duration
-        self._append_instruction(t_pulse, t_end, waveform_index)
-
-    def shift_phase(self, t, phase_shift):
-        self._flush_phase_shifts(t)
-        if self._pending_phase_shift is not None:
-            pending_phase = self._pending_phase_shift.phase_shift
-        else:
-            pending_phase = 0
-        self._pending_phase_shift = PhaseShift(t, pending_phase + phase_shift)
+        waveform = self._render_waveform(iq_pulse)
+        self._add_waveform(t_pulse, waveform)
 
     def chirp(self, t_pulse, chirp):
-        self._flush_phase_shifts(t_pulse)
-        offset = self._get_wvf_offset(t_pulse)
-        waveform_index, duration = self._render_chirp(chirp, offset)
-        t_end = t_pulse + duration
-        self._append_instruction(t_pulse, t_end, waveform_index)
-
-    def _flush_phase_shifts(self, t):
-        pending = self._pending_phase_shift
-        if pending is not None and pending.t < t:
-            self._append_phase_shift(pending.t, pending.phase_shift)
-            self._pending_phase_shift = None
-
-    def _append_phase_shift(self, t, phase_shift):
-        waveform_index, duration = self._render_phase_shift(phase_shift)
-        t_end = t + duration
-        self._append_instruction(t, t_end, waveform_index)
+        # TODO: Frequency chirp with prescaler: pass as FM i.s.o. PM, or Chirp?
+        waveform = self._render_chirp(chirp)
+        self._add_waveform(t_pulse, waveform)
 
     def conditional_pulses(self, t_instr, segment_start, pulses, order,
                            condition_register):
-
-        self._flush_phase_shifts(t_instr)
-        if self._pending_phase_shift is not None:
-            raise Exception('Error joining phase shift with conditional pulse')
-        self._wait_till(t_instr)
-        # start of aligned waveform
-        t_instr = self.time
+        self._push_instruction()
+        t_instr = iround(t_instr) // 5 * 5
 
         entry = SequenceConditionalEntry(cr=condition_register)
-        self.sequence.append(entry)
-        self.last_instruction = entry
 
         t_end = t_instr
         wvf_indices = []
         for pulse in pulses:
             if pulse is None:
                 # a do nothing pulse
-                index, duration = self._render_phase_shift(0)
-                pulse_end = t_instr + duration
+                waveform = Waveform(duration=1)
+                pulse_end = t_instr + 1
             elif pulse.mw_pulse is not None:
                 mw_entry = pulse.mw_pulse
                 t_pulse = segment_start + mw_entry.start
-                wvf_offset = iround(t_pulse - t_instr)
-                index, duration = self._render_waveform(mw_entry, wvf_offset,
-                                                        prephase=pulse.prephase,
-                                                        postphase=pulse.postphase)
-                pulse_end = t_pulse + duration
+                wvf_offset = iround(t_pulse) - t_instr
+                waveform = self._render_waveform(mw_entry, prephase=pulse.prephase, postphase=pulse.postphase)
+                waveform.offset = wvf_offset
             else:
-                index, duration = self._render_phase_shift(pulse.prephase)
-                pulse_end = t_instr + duration
+                waveform = Waveform(duration=2, prephase=pulse.prephase)
+
+            index = self._get_waveform_index(waveform)
             wvf_indices.append(index)
+            pulse_end = t_instr + waveform.instruction_duration
             t_end = max(t_end, pulse_end)
 
-        self._set_pulse_end(t_end)
-
         for ibranch in order:
             entry.waveform_indices.append(wvf_indices[ibranch])
 
-    def close(self):
-        pending = self._pending_phase_shift
-        if pending is not None:
-            self._append_phase_shift(pending.t, pending.phase_shift)
-            self._pending_phase_shift = None
+        self._append_sequence_entry(t_instr, entry)
+        self._set_pulse_end(t_end)
 
+    def close(self):
+        self._push_instruction()
         # set wait time of last instruction
-        if self.last_instruction is not None and self.end_pulse > self.time:
+        last_entry = self._get_last_entry()
+        if last_entry is not None and self.end_pulse > self.time:
             t_wait = self.end_pulse - self.time
             t = (iround(t_wait) // 5 + 1) * 5
-            self.last_instruction.time_after = t
-            self.last_instruction = None
+            last_entry.time_after = t
 
-    def _append_instruction(self, t_start, t_end, waveform_index):
-        self._wait_till(t_start)
+    def _render_chirp(self, chirp):
+        duration = iround(chirp.stop - chirp.start)
+        frequency = chirp.start_frequency - self.lo_freq
+        if abs(frequency) > 450e6:
+            raise Exception(f'Chirp NCO frequency {frequency/1e6:5.1f} MHz is out of range')
+        end_frequency = chirp.stop_frequency - self.lo_freq
+        if abs(end_frequency) > 450e6:
+            raise Exception(f'Chirp NCO frequency {end_frequency/1e6:5.1f} MHz is out of range')
+        ph_gen = chirp.phase_mod_generator()
+        return Waveform(chirp.amplitude, 1.0, frequency,
+                        ph_gen(duration, 1.0),
+                        0, 0, duration)
+
+    def _render_waveform(self, mw_pulse_data, prephase=0, postphase=0):
+        # always render at 1e9 Sa/s
+        duration = iround(mw_pulse_data.stop) - iround(mw_pulse_data.start)
+        if mw_pulse_data.envelope is None:
+            amp_envelope = 1.0
+            pm_envelope = 0.0
+        else:
+            amp_envelope = mw_pulse_data.envelope.get_AM_envelope(duration, 1.0)
+            pm_envelope = mw_pulse_data.envelope.get_PM_envelope(duration, 1.0)
+
+        frequency = mw_pulse_data.frequency - self.lo_freq
+        if abs(frequency) > 450e6:
+            raise Exception(f'Waveform NCO frequency {frequency/1e6:5.1f} MHz is out of range')
+
+        prephase += mw_pulse_data.start_phase
+        postphase -= mw_pulse_data.start_phase
+        return Waveform(mw_pulse_data.amplitude, amp_envelope,
+                        frequency, pm_envelope,
+                        prephase, postphase, duration)
+
+    def _add_waveform(self, t, waveform):
+        t = iround(t)
+        if t < self.time:
+            raise Exception(f'Oops! Pulses should be rendered in right order')
+        offset = t % 5
+        t_instruction = t - offset
+        waveform.offset = offset
+
+        if self.pending_instruction:
+            pending = self.pending_instruction
+            if t_instruction >= pending.t_instruction_end:
+                self._push_instruction()
+                self.pending_instruction = Instruction(t_instruction, waveform=waveform)
+            elif pending.waveform:
+                raise Exception(f'Overlapping MW pulses at {t_instruction}')
+            elif pending.phase_shift is not None:
+                waveform.prephase += pending.phase_shift
+                pending.waveform = waveform
+                pending.phase_shift = None
+            else:
+                raise Exception('Oops! instruction without waveform or pulse')
+        else:
+            # create new instruction with waveform
+            self.pending_instruction = Instruction(t_instruction, waveform=waveform)
+
+    def _add_phase_shift(self, t, phase_shift):
+        t = iround(t)
+        if t < self.time:
+            raise Exception(f'Oops! Pulses should be rendered in right order')
+        offset = t % 5
+        t_instruction = t - offset
+
+        if self.pending_instruction:
+            pending = self.pending_instruction
+            if t_instruction >= pending.t_instruction_end:
+                self._push_instruction()
+                self.pending_instruction = Instruction(t_instruction, phase_shift=phase_shift)
+            elif pending.waveform:
+                if t < pending.t_pulse_end:
+                    raise Exception(f'Cannot shift phase during MW pulse at {t}')
+                else:
+                    pending.waveform.postphase += phase_shift
+            elif pending.phase_shift is not None:
+                pending.phase_shift += phase_shift
+            else:
+                raise Exception('Oops! instruction without waveform or pulse')
+        else:
+            # create new instruction with phase_shift
+            self.pending_instruction = Instruction(t_instruction, phase_shift=phase_shift)
+
+    def _push_instruction(self):
+        pending = self.pending_instruction
+        if not pending:
+            return
+        if pending.waveform:
+            waveform = pending.waveform
+        elif pending.phase_shift is not None:
+            if pending.phase_shift != 0.0:
+                waveform = Waveform(prephase=pending.phase_shift, duration=2)
+            else:
+                return
+        else:
+            raise Exception('Oops! instruction without waveform or pulse')
         entry = SequenceEntry()
-        entry.waveform_index = waveform_index
-        self.sequence.append(entry)
-        self.last_instruction = entry
-        self._set_pulse_end(t_end)
+        entry.waveform_index = self._get_waveform_index(waveform)
+        self._append_sequence_entry(pending.t_start, entry)
+        self._set_pulse_end(pending.t_instruction_end)
+        self.pending_instruction = None
 
     def _set_pulse_end(self, t_end):
         self.end_pulse = max(self.end_pulse, iround(t_end))
@@ -180,19 +274,19 @@ class IQSequenceBuilder:
         if t_instruction < self.end_pulse:
             raise Exception(f'Overlapping pulses at {t_start} of {self.name}')
 
+        last_entry = self._get_last_entry()
         t_wait = t_instruction - self.time
-        if self.last_instruction is not None and t_wait < 5:
-            raise Exception(f'wait < 5 {self.name} at {t_start}')
-        elif self.last_instruction is None and t_wait < 0:
+        if t_wait < 0:
             raise Exception(f'wait < 0 {self.name} at {t_start}')
+        if last_entry is not None and t_wait < 5:
+            raise Exception(f'wait < 5 {self.name} at {t_start}')
 
         # set wait time of last instruction
-        if self.last_instruction is not None:
+        if last_entry is not None:
             # Max wait time for instruction with waveform ~ 32 ms
             max_wait = 5 * (2**16-1)
             t = min(max_wait, t_wait)
-            self.last_instruction.time_after = t
-            self.last_instruction = None
+            last_entry.time_after = t
             t_wait -= t
 
         # insert additional instructions till t_start
@@ -207,56 +301,14 @@ class IQSequenceBuilder:
 
         self.time = t_instruction
 
-    def _render_chirp(self, chirp, offset):
-        duration = iround(chirp.stop - chirp.start)
-        frequency = chirp.start_frequency - self.lo_freq
-        ph_gen = chirp.phase_mod_generator()
-        waveform = Waveform(chirp.amplitude, 1.0, frequency,
-                            ph_gen(duration, 1.0),
-                            0, 0, duration, offset)
-
-        # post_phase and pm_envelope add 2 samples, but last sample is restore of NCO
-        # frequency and can be overwritten by next pulse without consequences.
-        extra = 1
-
-        index = self._get_waveform_index(waveform)
-        return index, duration + extra
-
-    def _render_waveform(self, mw_pulse_data, offset:float, prephase=0, postphase=0):
-        # always render at 1e9 Sa/s
-        duration = iround(mw_pulse_data.stop) - iround(mw_pulse_data.start)
-        if mw_pulse_data.envelope is None:
-            amp_envelope = 1.0
-            pm_envelope = 0.0
-            add_pm = False
-        else:
-            amp_envelope = mw_pulse_data.envelope.get_AM_envelope(duration, 1.0)
-            pm_envelope = mw_pulse_data.envelope.get_PM_envelope(duration, 1.0)
-            add_pm = not np.all(pm_envelope == 0)
-
-        frequency = mw_pulse_data.frequency - self.lo_freq
-        if abs(frequency) > 450e6:
-            raise Exception(f'Waveform frequency {frequency/1e6:5.1f} MHz out of range')
-
-        waveform = Waveform(mw_pulse_data.amplitude, amp_envelope,
-                            frequency, pm_envelope,
-                            mw_pulse_data.start_phase + prephase,
-                            -mw_pulse_data.start_phase + postphase,
-                            duration, offset)
-
-        extra = 0
-        # post_phase and pm_envelope add 2 samples, but last sample is restore of NCO
-        # frequency and can be overwritten by next pulse without consequences.
-        if -mw_pulse_data.start_phase + postphase != 0 or add_pm:
-            extra = 1
-
-        index = self._get_waveform_index(waveform)
-        return index, duration + extra
+    def _append_sequence_entry(self, t_start, entry):
+        self._wait_till(t_start)
+        self.sequence.append(entry)
 
-    def _render_phase_shift(self, phase_shift) -> Waveform:
-        waveform = Waveform(prephase=phase_shift, duration=2)
-        index = self._get_waveform_index(waveform)
-        return index, 1
+    def _get_last_entry(self):
+        if len(self.sequence):
+            return self.sequence[-1]
+        return None
 
     def _get_waveform_index(self, waveform:Waveform):
         try:
@@ -266,9 +318,6 @@ class IQSequenceBuilder:
             self.waveforms.append(waveform)
         return index
 
-    def _get_wvf_offset(self, t_pulse):
-        return iround(t_pulse) % 5
-
 
 class AcquisitionSequenceBuilder:
     def __init__(self, name):
diff --git a/pulse_lib/keysight/qs_uploader.py b/pulse_lib/keysight/qs_uploader.py
index 56fdb3e8bc087fec20279a82efc5c440ce3bb915..e94c311699e2fce9459f3ae3a65895c358b33553 100644
--- a/pulse_lib/keysight/qs_uploader.py
+++ b/pulse_lib/keysight/qs_uploader.py
@@ -959,9 +959,8 @@ class UploadAggregator:
                 raise Exception(f'I/Q Channel delays must be equal ({channel_name})')
 
             sequencer_offset = self.sequencer_channels[channel_name].sequencer_offset
-            # TODO improve for alignment on 1 ns. -> set channel delay in FPGA
             # subtract offset, because it's started before 'classical' queued waveform
-            t_start = int((-self.max_pre_start_ns - delays[0]) / 5) * 5 - sequencer_offset
+            t_start = int(-self.max_pre_start_ns - delays[0]) - sequencer_offset
 
             sequence = IQSequenceBuilder(channel_name, t_start,
                                          qubit_channel.iq_channel.LO)
diff --git a/pulse_lib/tests/test_iq/test_iq_qs.py b/pulse_lib/tests/test_iq/test_iq_qs.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a7572e6e5d117a4dac583987bc0b3a6ad7efe1c
--- /dev/null
+++ b/pulse_lib/tests/test_iq/test_iq_qs.py
@@ -0,0 +1,32 @@
+
+from pulse_lib.tests.configurations.test_configuration import context
+
+def test():
+    pulse = context.init_pulselib(n_qubits=2)
+
+    s = pulse.mk_segment()
+
+    s.q1.add_phase_shift(0, -0.1)
+    s.q1.add_phase_shift(5, 0.1)
+    s.q1.add_phase_shift(6, 0.1)
+    s.q1.add_MW_pulse(10, 21, 50, 2.45e9)
+    s.q1.add_phase_shift(21, 0.2)
+    s.q1.add_phase_shift(24, 0.05)
+
+    s.q1.add_phase_shift(26, 0.3)
+
+    s.q1.add_phase_shift(30, 0.4)
+    s.q1.add_MW_pulse(31, 40, 200, 2.45e9)
+    s.q1.add_phase_shift(40, 0.5)
+
+    context.plot_segments([s])
+
+    sequence = pulse.mk_sequence([s])
+    context.add_hw_schedule(sequence)
+    context.plot_awgs(sequence, ylim=(-0.100,0.100))
+    context.pulse.awg_devices['AWG3'].describe()
+
+    return None
+
+if __name__ == '__main__':
+    ds = test()