v3d: Fix copy-propagation of input unpacks.
[mesa.git] / src / broadcom / compiler / qpu_schedule.c
index 799da805906daa9f2ed614ca958dfcf0f562b8f6..0f8001ff52d24889df2e74625a3bf62ebab4fc9d 100644 (file)
@@ -78,11 +78,13 @@ struct schedule_node_child {
 enum direction { F, R };
 
 struct schedule_state {
+        const struct v3d_device_info *devinfo;
         struct schedule_node *last_r[6];
         struct schedule_node *last_rf[64];
         struct schedule_node *last_sf;
         struct schedule_node *last_vpm_read;
         struct schedule_node *last_tmu_write;
+        struct schedule_node *last_tmu_config;
         struct schedule_node *last_tlb;
         struct schedule_node *last_vpm;
         struct schedule_node *last_unif;
@@ -193,7 +195,20 @@ process_waddr_deps(struct schedule_state *state, struct schedule_node *n,
         if (!magic) {
                 add_write_dep(state, &state->last_rf[waddr], n);
         } else if (v3d_qpu_magic_waddr_is_tmu(waddr)) {
+                /* XXX perf: For V3D 4.x, we could reorder TMU writes other
+                 * than the TMUS/TMUD/TMUA to improve scheduling flexibility.
+                 */
                 add_write_dep(state, &state->last_tmu_write, n);
+                switch (waddr) {
+                case V3D_QPU_WADDR_TMUS:
+                case V3D_QPU_WADDR_TMUSCM:
+                case V3D_QPU_WADDR_TMUSF:
+                case V3D_QPU_WADDR_TMUSLOD:
+                        add_write_dep(state, &state->last_tmu_config, n);
+                        break;
+                default:
+                        break;
+                }
         } else if (v3d_qpu_magic_waddr_is_sfu(waddr)) {
                 /* Handled by v3d_qpu_writes_r4() check. */
         } else {
@@ -221,6 +236,16 @@ process_waddr_deps(struct schedule_state *state, struct schedule_node *n,
                         add_write_dep(state, &state->last_tlb, n);
                         break;
 
+                case V3D_QPU_WADDR_SYNC:
+                case V3D_QPU_WADDR_SYNCB:
+                case V3D_QPU_WADDR_SYNCU:
+                        /* For CS barrier(): Sync against any other memory
+                         * accesses.  There doesn't appear to be any need for
+                         * barriers to affect ALU operations.
+                         */
+                        add_write_dep(state, &state->last_tmu_write, n);
+                        break;
+
                 case V3D_QPU_WADDR_NOP:
                         break;
 
@@ -231,30 +256,6 @@ process_waddr_deps(struct schedule_state *state, struct schedule_node *n,
         }
 }
 
-static void
-process_cond_deps(struct schedule_state *state, struct schedule_node *n,
-                  enum v3d_qpu_cond cond)
-{
-        if (cond != V3D_QPU_COND_NONE)
-                add_read_dep(state, state->last_sf, n);
-}
-
-static void
-process_pf_deps(struct schedule_state *state, struct schedule_node *n,
-                enum v3d_qpu_pf pf)
-{
-        if (pf != V3D_QPU_PF_NONE)
-                add_write_dep(state, &state->last_sf, n);
-}
-
-static void
-process_uf_deps(struct schedule_state *state, struct schedule_node *n,
-                enum v3d_qpu_uf uf)
-{
-        if (uf != V3D_QPU_UF_NONE)
-                add_write_dep(state, &state->last_sf, n);
-}
-
 /**
  * Common code for dependencies that need to be tracked both forward and
  * backward.
@@ -265,8 +266,14 @@ process_uf_deps(struct schedule_state *state, struct schedule_node *n,
 static void
 calculate_deps(struct schedule_state *state, struct schedule_node *n)
 {
+        const struct v3d_device_info *devinfo = state->devinfo;
         struct qinst *qinst = n->inst;
         struct v3d_qpu_instr *inst = &qinst->qpu;
+        /* If the input and output segments are shared, then all VPM reads to
+         * a location need to happen before all writes.  We handle this by
+         * serializing all VPM operations for now.
+         */
+        bool separate_vpm_segment = false;
 
         if (inst->type == V3D_QPU_INSTR_TYPE_BRANCH) {
                 if (inst->branch.cond != V3D_QPU_BRANCH_COND_ALWAYS)
@@ -308,6 +315,18 @@ calculate_deps(struct schedule_state *state, struct schedule_node *n)
                 add_write_dep(state, &state->last_vpm, n);
                 break;
 
+        case V3D_QPU_A_LDVPMV_IN:
+        case V3D_QPU_A_LDVPMD_IN:
+        case V3D_QPU_A_LDVPMG_IN:
+        case V3D_QPU_A_LDVPMP:
+                if (!separate_vpm_segment)
+                        add_write_dep(state, &state->last_vpm, n);
+                break;
+
+        case V3D_QPU_A_VPMWT:
+                add_read_dep(state, state->last_vpm, n);
+                break;
+
         case V3D_QPU_A_MSF:
                 add_read_dep(state, state->last_tlb, n);
                 break;
@@ -317,19 +336,6 @@ calculate_deps(struct schedule_state *state, struct schedule_node *n)
                 add_write_dep(state, &state->last_tlb, n);
                 break;
 
-        case V3D_QPU_A_FLAPUSH:
-        case V3D_QPU_A_FLBPUSH:
-        case V3D_QPU_A_VFLA:
-        case V3D_QPU_A_VFLNA:
-        case V3D_QPU_A_VFLB:
-        case V3D_QPU_A_VFLNB:
-                add_read_dep(state, state->last_sf, n);
-                break;
-
-        case V3D_QPU_A_FLBPOP:
-                add_write_dep(state, &state->last_sf, n);
-                break;
-
         default:
                 break;
         }
@@ -356,12 +362,16 @@ calculate_deps(struct schedule_state *state, struct schedule_node *n)
                 process_waddr_deps(state, n, inst->alu.mul.waddr,
                                    inst->alu.mul.magic_write);
         }
+        if (v3d_qpu_sig_writes_address(devinfo, &inst->sig)) {
+                process_waddr_deps(state, n, inst->sig_addr,
+                                   inst->sig_magic);
+        }
 
-        if (v3d_qpu_writes_r3(inst))
+        if (v3d_qpu_writes_r3(devinfo, inst))
                 add_write_dep(state, &state->last_r[3], n);
-        if (v3d_qpu_writes_r4(inst))
+        if (v3d_qpu_writes_r4(devinfo, inst))
                 add_write_dep(state, &state->last_r[4], n);
-        if (v3d_qpu_writes_r5(inst))
+        if (v3d_qpu_writes_r5(devinfo, inst))
                 add_write_dep(state, &state->last_r[5], n);
 
         if (inst->sig.thrsw) {
@@ -371,6 +381,7 @@ calculate_deps(struct schedule_state *state, struct schedule_node *n)
                 for (int i = 0; i < ARRAY_SIZE(state->last_r); i++)
                         add_write_dep(state, &state->last_r[i], n);
                 add_write_dep(state, &state->last_sf, n);
+                add_write_dep(state, &state->last_rtop, n);
 
                 /* Scoreboard-locking operations have to stay after the last
                  * thread switch.
@@ -378,30 +389,39 @@ calculate_deps(struct schedule_state *state, struct schedule_node *n)
                 add_write_dep(state, &state->last_tlb, n);
 
                 add_write_dep(state, &state->last_tmu_write, n);
+                add_write_dep(state, &state->last_tmu_config, n);
         }
 
-        if (inst->sig.ldtmu) {
+        if (v3d_qpu_waits_on_tmu(inst)) {
                 /* TMU loads are coming from a FIFO, so ordering is important.
                  */
                 add_write_dep(state, &state->last_tmu_write, n);
         }
 
+        if (inst->sig.wrtmuc)
+                add_write_dep(state, &state->last_tmu_config, n);
+
         if (inst->sig.ldtlb | inst->sig.ldtlbu)
                 add_read_dep(state, state->last_tlb, n);
 
-        if (inst->sig.ldvpm)
+        if (inst->sig.ldvpm) {
                 add_write_dep(state, &state->last_vpm_read, n);
 
+                /* At least for now, we're doing shared I/O segments, so queue
+                 * all writes after all reads.
+                 */
+                if (!separate_vpm_segment)
+                        add_write_dep(state, &state->last_vpm, n);
+        }
+
         /* inst->sig.ldunif or sideband uniform read */
         if (qinst->uniform != ~0)
                 add_write_dep(state, &state->last_unif, n);
 
-        process_cond_deps(state, n, inst->flags.ac);
-        process_cond_deps(state, n, inst->flags.mc);
-        process_pf_deps(state, n, inst->flags.apf);
-        process_pf_deps(state, n, inst->flags.mpf);
-        process_uf_deps(state, n, inst->flags.auf);
-        process_uf_deps(state, n, inst->flags.muf);
+        if (v3d_qpu_reads_flags(inst))
+                add_read_dep(state, state->last_sf, n);
+        if (v3d_qpu_writes_flags(inst))
+                add_write_dep(state, &state->last_sf, n);
 }
 
 static void
@@ -410,6 +430,7 @@ calculate_forward_deps(struct v3d_compile *c, struct list_head *schedule_list)
         struct schedule_state state;
 
         memset(&state, 0, sizeof(state));
+        state.devinfo = c->devinfo;
         state.dir = F;
 
         list_for_each_entry(struct schedule_node, node, schedule_list, link)
@@ -423,6 +444,7 @@ calculate_reverse_deps(struct v3d_compile *c, struct list_head *schedule_list)
         struct schedule_state state;
 
         memset(&state, 0, sizeof(state));
+        state.devinfo = c->devinfo;
         state.dir = R;
 
         for (node = schedule_list->prev; schedule_list != node; node = node->prev) {
@@ -432,10 +454,10 @@ calculate_reverse_deps(struct v3d_compile *c, struct list_head *schedule_list)
 
 struct choose_scoreboard {
         int tick;
-        int last_sfu_write_tick;
+        int last_magic_sfu_write_tick;
         int last_ldvary_tick;
         int last_uniforms_reset_tick;
-        uint32_t last_waddr_add, last_waddr_mul;
+        int last_thrsw_tick;
         bool tlb_locked;
 };
 
@@ -444,22 +466,8 @@ mux_reads_too_soon(struct choose_scoreboard *scoreboard,
                    const struct v3d_qpu_instr *inst, enum v3d_qpu_mux mux)
 {
         switch (mux) {
-        case V3D_QPU_MUX_A:
-                if (scoreboard->last_waddr_add == inst->raddr_a ||
-                    scoreboard->last_waddr_mul == inst->raddr_a) {
-                        return true;
-                }
-                break;
-
-        case V3D_QPU_MUX_B:
-                if (scoreboard->last_waddr_add == inst->raddr_b ||
-                    scoreboard->last_waddr_mul == inst->raddr_b) {
-                        return true;
-                }
-                break;
-
         case V3D_QPU_MUX_R4:
-                if (scoreboard->tick - scoreboard->last_sfu_write_tick <= 2)
+                if (scoreboard->tick - scoreboard->last_magic_sfu_write_tick <= 2)
                         return true;
                 break;
 
@@ -514,7 +522,8 @@ reads_too_soon_after_write(struct choose_scoreboard *scoreboard,
 }
 
 static bool
-writes_too_soon_after_write(struct choose_scoreboard *scoreboard,
+writes_too_soon_after_write(const struct v3d_device_info *devinfo,
+                            struct choose_scoreboard *scoreboard,
                             struct qinst *qinst)
 {
         const struct v3d_qpu_instr *inst = &qinst->qpu;
@@ -523,8 +532,8 @@ writes_too_soon_after_write(struct choose_scoreboard *scoreboard,
          * This would normally be prevented by dependency tracking, but might
          * occur if a dead SFU computation makes it to scheduling.
          */
-        if (scoreboard->tick - scoreboard->last_sfu_write_tick < 2 &&
-            v3d_qpu_writes_r4(inst))
+        if (scoreboard->tick - scoreboard->last_magic_sfu_write_tick < 2 &&
+            v3d_qpu_writes_r4(devinfo, inst))
                 return true;
 
         return false;
@@ -551,22 +560,21 @@ get_instruction_priority(const struct v3d_qpu_instr *inst)
         next_score++;
 
         /* Schedule texture read results collection late to hide latency. */
-        if (inst->sig.ldtmu)
+        if (v3d_qpu_waits_on_tmu(inst))
                 return next_score;
         next_score++;
 
+        /* XXX perf: We should schedule SFU ALU ops so that the reader is 2
+         * instructions after the producer if possible, not just 1.
+         */
+
         /* Default score for things that aren't otherwise special. */
         baseline_score = next_score;
         next_score++;
 
         /* Schedule texture read setup early to hide their latency better. */
-        if (inst->type == V3D_QPU_INSTR_TYPE_ALU &&
-            ((inst->alu.add.magic_write &&
-              v3d_qpu_magic_waddr_is_tmu(inst->alu.add.waddr)) ||
-             (inst->alu.mul.magic_write &&
-              v3d_qpu_magic_waddr_is_tmu(inst->alu.mul.waddr)))) {
+        if (v3d_qpu_writes_tmu(inst))
                 return next_score;
-        }
         next_score++;
 
         return baseline_score;
@@ -585,6 +593,11 @@ qpu_magic_waddr_is_periph(enum v3d_qpu_waddr waddr)
 static bool
 qpu_accesses_peripheral(const struct v3d_qpu_instr *inst)
 {
+        if (v3d_qpu_uses_vpm(inst))
+                return true;
+        if (v3d_qpu_uses_sfu(inst))
+                return true;
+
         if (inst->type == V3D_QPU_INSTR_TYPE_ALU) {
                 if (inst->alu.add.op != V3D_QPU_A_NOP &&
                     inst->alu.add.magic_write &&
@@ -592,7 +605,7 @@ qpu_accesses_peripheral(const struct v3d_qpu_instr *inst)
                         return true;
                 }
 
-                if (inst->alu.add.op == V3D_QPU_A_VPMSETUP)
+                if (inst->alu.add.op == V3D_QPU_A_TMUWT)
                         return true;
 
                 if (inst->alu.mul.op != V3D_QPU_M_NOP &&
@@ -605,7 +618,8 @@ qpu_accesses_peripheral(const struct v3d_qpu_instr *inst)
         return (inst->sig.ldvpm ||
                 inst->sig.ldtmu ||
                 inst->sig.ldtlb ||
-                inst->sig.ldtlbu);
+                inst->sig.ldtlbu ||
+                inst->sig.wrtmuc);
 }
 
 static bool
@@ -619,7 +633,11 @@ qpu_merge_inst(const struct v3d_device_info *devinfo,
                 return false;
         }
 
-        /* Can't do more than one peripheral access in an instruction. */
+        /* Can't do more than one peripheral access in an instruction.
+         *
+         * XXX: V3D 4.1 allows TMU read along with a VPM read or write, and
+         * WRTMUC with a TMU magic register write (other than tmuc).
+         */
         if (qpu_accesses_peripheral(a) && qpu_accesses_peripheral(b))
                 return false;
 
@@ -655,7 +673,8 @@ qpu_merge_inst(const struct v3d_device_info *devinfo,
 
         if (v3d_qpu_uses_mux(b, V3D_QPU_MUX_B)) {
                 if (v3d_qpu_uses_mux(a, V3D_QPU_MUX_B) &&
-                    a->raddr_b != b->raddr_b) {
+                    (a->raddr_b != b->raddr_b ||
+                     a->sig.small_imm != b->sig.small_imm)) {
                         return false;
                 }
                 merge.raddr_b = b->raddr_b;
@@ -663,6 +682,9 @@ qpu_merge_inst(const struct v3d_device_info *devinfo,
 
         merge.sig.thrsw |= b->sig.thrsw;
         merge.sig.ldunif |= b->sig.ldunif;
+        merge.sig.ldunifrf |= b->sig.ldunifrf;
+        merge.sig.ldunifa |= b->sig.ldunifa;
+        merge.sig.ldunifarf |= b->sig.ldunifarf;
         merge.sig.ldtmu |= b->sig.ldtmu;
         merge.sig.ldvary |= b->sig.ldvary;
         merge.sig.ldvpm |= b->sig.ldvpm;
@@ -673,6 +695,12 @@ qpu_merge_inst(const struct v3d_device_info *devinfo,
         merge.sig.rotate |= b->sig.rotate;
         merge.sig.wrtmuc |= b->sig.wrtmuc;
 
+        if (v3d_qpu_sig_writes_address(devinfo, &a->sig) &&
+            v3d_qpu_sig_writes_address(devinfo, &b->sig))
+                return false;
+        merge.sig_addr |= b->sig_addr;
+        merge.sig_magic |= b->sig_magic;
+
         uint64_t packed;
         bool ok = v3d_qpu_instr_pack(devinfo, &merge, &packed);
 
@@ -719,7 +747,7 @@ choose_instruction_to_schedule(const struct v3d_device_info *devinfo,
                 if (reads_too_soon_after_write(scoreboard, n->inst))
                         continue;
 
-                if (writes_too_soon_after_write(scoreboard, n->inst))
+                if (writes_too_soon_after_write(devinfo, scoreboard, n->inst))
                         continue;
 
                 /* "A scoreboard wait must not occur in the first two
@@ -734,8 +762,14 @@ choose_instruction_to_schedule(const struct v3d_device_info *devinfo,
                  * sooner.  If the ldvary's r5 wasn't used, then ldunif might
                  * otherwise get scheduled so ldunif and ldvary try to update
                  * r5 in the same tick.
+                 *
+                 * XXX perf: To get good pipelining of a sequence of varying
+                 * loads, we need to figure out how to pair the ldvary signal
+                 * up to the instruction before the last r5 user in the
+                 * previous ldvary sequence.  Currently, it usually pairs with
+                 * the last r5 user.
                  */
-                if (inst->sig.ldunif &&
+                if ((inst->sig.ldunif || inst->sig.ldunifa) &&
                     scoreboard->tick == scoreboard->last_ldvary_tick + 1) {
                         continue;
                 }
@@ -803,16 +837,13 @@ update_scoreboard_for_magic_waddr(struct choose_scoreboard *scoreboard,
                                   enum v3d_qpu_waddr waddr)
 {
         if (v3d_qpu_magic_waddr_is_sfu(waddr))
-                scoreboard->last_sfu_write_tick = scoreboard->tick;
+                scoreboard->last_magic_sfu_write_tick = scoreboard->tick;
 }
 
 static void
 update_scoreboard_for_chosen(struct choose_scoreboard *scoreboard,
                              const struct v3d_qpu_instr *inst)
 {
-        scoreboard->last_waddr_add = ~0;
-        scoreboard->last_waddr_mul = ~0;
-
         if (inst->type == V3D_QPU_INSTR_TYPE_BRANCH)
                 return;
 
@@ -822,8 +853,6 @@ update_scoreboard_for_chosen(struct choose_scoreboard *scoreboard,
                 if (inst->alu.add.magic_write) {
                         update_scoreboard_for_magic_waddr(scoreboard,
                                                           inst->alu.add.waddr);
-                } else {
-                        scoreboard->last_waddr_add = inst->alu.add.waddr;
                 }
         }
 
@@ -831,8 +860,6 @@ update_scoreboard_for_chosen(struct choose_scoreboard *scoreboard,
                 if (inst->alu.mul.magic_write) {
                         update_scoreboard_for_magic_waddr(scoreboard,
                                                           inst->alu.mul.waddr);
-                } else {
-                        scoreboard->last_waddr_mul = inst->alu.mul.waddr;
                 }
         }
 
@@ -892,7 +919,7 @@ static uint32_t magic_waddr_latency(enum v3d_qpu_waddr waddr,
          *
          * because we associate the first load_tmu0 with the *second* tmu0_s.
          */
-        if (v3d_qpu_magic_waddr_is_tmu(waddr) && after->sig.ldtmu)
+        if (v3d_qpu_magic_waddr_is_tmu(waddr) && v3d_qpu_waits_on_tmu(after))
                 return 100;
 
         /* Assume that anything depending on us is consuming the SFU result. */
@@ -985,6 +1012,19 @@ mark_instruction_scheduled(struct list_head *schedule_list,
         }
 }
 
+static void
+insert_scheduled_instruction(struct v3d_compile *c,
+                             struct qblock *block,
+                             struct choose_scoreboard *scoreboard,
+                             struct qinst *inst)
+{
+        list_addtail(&inst->link, &block->instructions);
+
+        update_scoreboard_for_chosen(scoreboard, &inst->qpu);
+        c->qpu_inst_count++;
+        scoreboard->tick++;
+}
+
 static struct qinst *
 vir_nop()
 {
@@ -994,61 +1034,190 @@ vir_nop()
         return qinst;
 }
 
-#if 0
-static struct qinst *
-nop_after(struct qinst *inst)
+static void
+emit_nop(struct v3d_compile *c, struct qblock *block,
+         struct choose_scoreboard *scoreboard)
 {
-        struct qinst *q = vir_nop();
+        insert_scheduled_instruction(c, block, scoreboard, vir_nop());
+}
+
+static bool
+qpu_instruction_valid_in_thrend_slot(struct v3d_compile *c,
+                                     const struct qinst *qinst, int slot)
+{
+        const struct v3d_qpu_instr *inst = &qinst->qpu;
+
+        /* Only TLB Z writes are prohibited in the last slot, but we don't
+         * have those flagged so prohibit all TLB ops for now.
+         */
+        if (slot == 2 && qpu_inst_is_tlb(inst))
+                return false;
+
+        if (slot > 0 && qinst->uniform != ~0)
+                return false;
+
+        if (v3d_qpu_uses_vpm(inst))
+                return false;
+
+        if (inst->sig.ldvary)
+                return false;
+
+        if (inst->type == V3D_QPU_INSTR_TYPE_ALU) {
+                /* GFXH-1625: TMUWT not allowed in the final instruction. */
+                if (slot == 2 && inst->alu.add.op == V3D_QPU_A_TMUWT)
+                        return false;
+
+                /* No writing physical registers at the end. */
+                if (!inst->alu.add.magic_write ||
+                    !inst->alu.mul.magic_write) {
+                        return false;
+                }
 
-        list_add(&q->link, &inst->link);
+                if (c->devinfo->ver < 40 && inst->alu.add.op == V3D_QPU_A_SETMSF)
+                        return false;
+
+                /* RF0-2 might be overwritten during the delay slots by
+                 * fragment shader setup.
+                 */
+                if (inst->raddr_a < 3 &&
+                    (inst->alu.add.a == V3D_QPU_MUX_A ||
+                     inst->alu.add.b == V3D_QPU_MUX_A ||
+                     inst->alu.mul.a == V3D_QPU_MUX_A ||
+                     inst->alu.mul.b == V3D_QPU_MUX_A)) {
+                        return false;
+                }
 
-        return q;
+                if (inst->raddr_b < 3 &&
+                    !inst->sig.small_imm &&
+                    (inst->alu.add.a == V3D_QPU_MUX_B ||
+                     inst->alu.add.b == V3D_QPU_MUX_B ||
+                     inst->alu.mul.a == V3D_QPU_MUX_B ||
+                     inst->alu.mul.b == V3D_QPU_MUX_B)) {
+                        return false;
+                }
+        }
+
+        return true;
+}
+
+static bool
+valid_thrsw_sequence(struct v3d_compile *c, struct choose_scoreboard *scoreboard,
+                     struct qinst *qinst, int instructions_in_sequence,
+                     bool is_thrend)
+{
+        /* No emitting our thrsw while the previous thrsw hasn't happened yet. */
+        if (scoreboard->last_thrsw_tick + 3 >
+            scoreboard->tick - instructions_in_sequence) {
+                return false;
+        }
+
+        for (int slot = 0; slot < instructions_in_sequence; slot++) {
+                /* No scheduling SFU when the result would land in the other
+                 * thread.  The simulator complains for safety, though it
+                 * would only occur for dead code in our case.
+                 */
+                if (slot > 0 &&
+                    qinst->qpu.type == V3D_QPU_INSTR_TYPE_ALU &&
+                    (v3d_qpu_magic_waddr_is_sfu(qinst->qpu.alu.add.waddr) ||
+                     v3d_qpu_magic_waddr_is_sfu(qinst->qpu.alu.mul.waddr))) {
+                        return false;
+                }
+
+                if (slot > 0 && qinst->qpu.sig.ldvary)
+                        return false;
+
+                if (is_thrend &&
+                    !qpu_instruction_valid_in_thrend_slot(c, qinst, slot)) {
+                        return false;
+                }
+
+                /* Note that the list is circular, so we can only do this up
+                 * to instructions_in_sequence.
+                 */
+                qinst = (struct qinst *)qinst->link.next;
+        }
+
+        return true;
 }
 
 /**
- * Emits a THRSW/LTHRSW signal in the stream, trying to move it up to pair
- * with another instruction.
+ * Emits a THRSW signal in the stream, trying to move it up to pair with
+ * another instruction.
  */
-static void
+static int
 emit_thrsw(struct v3d_compile *c,
+           struct qblock *block,
            struct choose_scoreboard *scoreboard,
-           const struct v3d_qpu_instr *inst)
+           struct qinst *inst,
+           bool is_thrend)
 {
+        int time = 0;
+
         /* There should be nothing in a thrsw inst being scheduled other than
          * the signal bits.
          */
-        assert(inst->type == V3D_QPU_INSTR_TYPE_ALU);
-        assert(inst->alu.add.op == V3D_QPU_A_NOP);
-        assert(inst->alu.mul.op == V3D_QPU_M_NOP);
+        assert(inst->qpu.type == V3D_QPU_INSTR_TYPE_ALU);
+        assert(inst->qpu.alu.add.op == V3D_QPU_A_NOP);
+        assert(inst->qpu.alu.mul.op == V3D_QPU_M_NOP);
+
+        /* Find how far back into previous instructions we can put the THRSW. */
+        int slots_filled = 0;
+        struct qinst *merge_inst = NULL;
+        vir_for_each_inst_rev(prev_inst, block) {
+                struct v3d_qpu_sig sig = prev_inst->qpu.sig;
+                sig.thrsw = true;
+                uint32_t packed_sig;
+
+                if (!v3d_qpu_sig_pack(c->devinfo, &sig, &packed_sig))
+                        break;
 
-        /* Try to find an earlier scheduled instruction that we can merge the
-         * thrsw into.
-         */
-        int thrsw_ip = c->qpu_inst_count;
-        for (int i = 1; i <= MIN2(c->qpu_inst_count, 3); i++) {
-                uint64_t prev_instr = c->qpu_insts[c->qpu_inst_count - i];
-                uint32_t prev_sig = QPU_GET_FIELD(prev_instr, QPU_SIG);
+                if (!valid_thrsw_sequence(c, scoreboard,
+                                          prev_inst, slots_filled + 1,
+                                          is_thrend)) {
+                        break;
+                }
 
-                if (prev_sig == QPU_SIG_NONE)
-                        thrsw_ip = c->qpu_inst_count - i;
+                merge_inst = prev_inst;
+                if (++slots_filled == 3)
+                        break;
         }
 
-        if (thrsw_ip != c->qpu_inst_count) {
-                /* Merge the thrsw into the existing instruction. */
-                c->qpu_insts[thrsw_ip] =
-                        QPU_UPDATE_FIELD(c->qpu_insts[thrsw_ip], sig, QPU_SIG);
+        bool needs_free = false;
+        if (merge_inst) {
+                merge_inst->qpu.sig.thrsw = true;
+                needs_free = true;
+                scoreboard->last_thrsw_tick = scoreboard->tick - slots_filled;
         } else {
-                qpu_serialize_one_inst(c, inst);
-                update_scoreboard_for_chosen(scoreboard, inst);
+                scoreboard->last_thrsw_tick = scoreboard->tick;
+                insert_scheduled_instruction(c, block, scoreboard, inst);
+                time++;
+                slots_filled++;
+                merge_inst = inst;
         }
 
-        /* Fill the delay slots. */
-        while (c->qpu_inst_count < thrsw_ip + 3) {
-                update_scoreboard_for_chosen(scoreboard, v3d_qpu_nop());
-                qpu_serialize_one_inst(c, v3d_qpu_nop());
+        /* Insert any extra delay slot NOPs we need. */
+        for (int i = 0; i < 3 - slots_filled; i++) {
+                emit_nop(c, block, scoreboard);
+                time++;
         }
+
+        /* If we're emitting the last THRSW (other than program end), then
+         * signal that to the HW by emitting two THRSWs in a row.
+         */
+        if (inst->is_last_thrsw) {
+                struct qinst *second_inst =
+                        (struct qinst *)merge_inst->link.next;
+                second_inst->qpu.sig.thrsw = true;
+        }
+
+        /* If we put our THRSW into another instruction, free up the
+         * instruction that didn't end up scheduled into the list.
+         */
+        if (needs_free)
+                free(inst);
+
+        return time;
 }
-#endif
 
 static uint32_t
 schedule_instructions(struct v3d_compile *c,
@@ -1097,6 +1266,13 @@ schedule_instructions(struct v3d_compile *c,
                         fprintf(stderr, "\n");
                 }
 
+                /* We can't mark_instruction_scheduled() the chosen inst until
+                 * we're done identifying instructions to merge, so put the
+                 * merged instructions on a list for a moment.
+                 */
+                struct list_head merged_list;
+                list_inithead(&merged_list);
+
                 /* Schedule this instruction onto the QPU list. Also try to
                  * find an instruction to pair with it.
                  */
@@ -1106,13 +1282,14 @@ schedule_instructions(struct v3d_compile *c,
                         mark_instruction_scheduled(schedule_list, time,
                                                    chosen, true);
 
-                        merge = choose_instruction_to_schedule(devinfo,
+                        while ((merge =
+                                choose_instruction_to_schedule(devinfo,
                                                                scoreboard,
                                                                schedule_list,
-                                                               chosen);
-                        if (merge) {
+                                                               chosen))) {
                                 time = MAX2(merge->unblocked_time, time);
                                 list_del(&merge->link);
+                                list_addtail(&merge->link, &merged_list);
                                 (void)qpu_merge_inst(devinfo, inst,
                                                      inst, &merge->inst->qpu);
                                 if (merge->inst->uniform != -1) {
@@ -1158,8 +1335,8 @@ schedule_instructions(struct v3d_compile *c,
                  * DAG edge as we do so.
                  */
                 mark_instruction_scheduled(schedule_list, time, chosen, false);
-
-                if (merge) {
+                list_for_each_entry(struct schedule_node, merge, &merged_list,
+                                    link) {
                         mark_instruction_scheduled(schedule_list, time, merge,
                                                    false);
 
@@ -1169,40 +1346,24 @@ schedule_instructions(struct v3d_compile *c,
                         free(merge->inst);
                 }
 
-                if (0 && inst->sig.thrsw) {
-                        /* XXX emit_thrsw(c, scoreboard, qinst); */
+                if (inst->sig.thrsw) {
+                        time += emit_thrsw(c, block, scoreboard, qinst, false);
                 } else {
-                        c->qpu_inst_count++;
-                        list_addtail(&qinst->link, &block->instructions);
-                        update_scoreboard_for_chosen(scoreboard, inst);
-                }
-
-                scoreboard->tick++;
-                time++;
-
-                if (inst->type == V3D_QPU_INSTR_TYPE_BRANCH ||
-                    inst->sig.thrsw /* XXX */) {
-                        block->branch_qpu_ip = c->qpu_inst_count - 1;
-                        /* Fill the delay slots.
-                         *
-                         * We should fill these with actual instructions,
-                         * instead, but that will probably need to be done
-                         * after this, once we know what the leading
-                         * instructions of the successors are (so we can
-                         * handle A/B register file write latency)
-                        */
-                        /* XXX: scoreboard */
-                        int slots = (inst->type == V3D_QPU_INSTR_TYPE_BRANCH ?
-                                     3 : 2);
-                        for (int i = 0; i < slots; i++) {
-                                struct qinst *nop = vir_nop();
-                                list_addtail(&nop->link, &block->instructions);
-
-                                update_scoreboard_for_chosen(scoreboard,
-                                                             &nop->qpu);
-                                c->qpu_inst_count++;
-                                scoreboard->tick++;
-                                time++;
+                        insert_scheduled_instruction(c, block,
+                                                     scoreboard, qinst);
+
+                        if (inst->type == V3D_QPU_INSTR_TYPE_BRANCH) {
+                                block->branch_qpu_ip = c->qpu_inst_count - 1;
+                                /* Fill the delay slots.
+                                 *
+                                 * We should fill these with actual instructions,
+                                 * instead, but that will probably need to be done
+                                 * after this, once we know what the leading
+                                 * instructions of the successors are (so we can
+                                 * handle A/B register file write latency)
+                                 */
+                                for (int i = 0; i < 3; i++)
+                                        emit_nop(c, block, scoreboard);
                         }
                 }
         }
@@ -1310,6 +1471,8 @@ uint32_t
 v3d_qpu_schedule_instructions(struct v3d_compile *c)
 {
         const struct v3d_device_info *devinfo = c->devinfo;
+        struct qblock *end_block = list_last_entry(&c->blocks,
+                                                   struct qblock, link);
 
         /* We reorder the uniforms as we schedule instructions, so save the
          * old data off and replace it.
@@ -1324,11 +1487,10 @@ v3d_qpu_schedule_instructions(struct v3d_compile *c)
 
         struct choose_scoreboard scoreboard;
         memset(&scoreboard, 0, sizeof(scoreboard));
-        scoreboard.last_waddr_add = ~0;
-        scoreboard.last_waddr_mul = ~0;
         scoreboard.last_ldvary_tick = -10;
-        scoreboard.last_sfu_write_tick = -10;
+        scoreboard.last_magic_sfu_write_tick = -10;
         scoreboard.last_uniforms_reset_tick = -10;
+        scoreboard.last_thrsw_tick = -10;
 
         if (debug) {
                 fprintf(stderr, "Pre-schedule instructions\n");
@@ -1359,6 +1521,11 @@ v3d_qpu_schedule_instructions(struct v3d_compile *c)
                 block->end_qpu_ip = c->qpu_inst_count - 1;
         }
 
+        /* Emit the program-end THRSW instruction. */;
+        struct qinst *thrsw = vir_nop();
+        thrsw->qpu.sig.thrsw = true;
+        emit_thrsw(c, end_block, &scoreboard, thrsw, true);
+
         qpu_set_branch_targets(c);
 
         assert(next_uniform == c->num_uniforms);