1 /* 2 * Generate a VGA signal using a PIC32 microcontroller. 3 * 4 * Copyright (C) 2017 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #include "mips.h" 21 #include "pic32.h" 22 23 #define LINE_LENGTH 160 /* pixels */ 24 25 #define HFREQ_LIMIT 1254 /* 40MHz cycles */ 26 #define LINE_LIMIT 960 /* 40MHz cycles (3 per byte/pixel) */ 27 #define HSYNC_LIMIT 96 /* 40MHz cycles (3 per byte/pixel) */ 28 29 #define HSYNC_START LINE_LIMIT 30 #define HSYNC_END (HSYNC_START + HSYNC_LIMIT) 31 32 #define VISIBLE_START 15 /* horizontal lines, back porch end */ 33 #define VFP_START 527 /* horizontal lines, front porch start */ 34 #define VSYNC_START 529 /* horizontal lines, front porch end */ 35 #define VSYNC_END 531 /* horizontal lines, back porch start */ 36 37 #define SCREEN_SIZE (40 * 1024) 38 39 /* Disable JTAG functionality on pins. */ 40 41 .section .devcfg0, "a" 42 .word 0xfffffffb /* DEVCFG0<2> = JTAGEN = 0 */ 43 44 /* 45 Set the oscillator to be the FRC oscillator with PLL, with peripheral clock 46 divided by 1, and FRCDIV+PLL selected. 47 48 The system clock and peripheral clock are therefore the same. 49 50 The watchdog timer (FWDTEN) is also disabled. 51 52 The secondary oscillator pin (FSOSCEN) is disabled to avoid pin conflicts with 53 RPB4. 54 */ 55 56 .section .devcfg1, "a" 57 .word 0xff7fcfd9 /* DEVCFG1<23> = FWDTEN = 0; DEVCFG1<13:12> = FPBDIV<2:0> = 0; 58 DEVCFG1<5> = FSOSCEN = 0; DEVCFG1<2:0> = FNOSC<2:0> = 001 */ 59 60 /* 61 Set the FRC oscillator PLL function with an input division of 4, an output 62 division of 2, a multiplication of 20, yielding a multiplication of 2.5. 63 64 The FRC is apparently at 16MHz and this produces a system clock of 40MHz. 65 */ 66 67 .section .devcfg2, "a" 68 .word 0xfff9ffdb /* DEVCFG2<18:16> = FPLLODIV<2:0> = 001; 69 DEVCFG2<6:4> = FPLLMUL<2:0> = 101; 70 DEVCFG2<2:0> = FPLLIDIV<2:0> = 011 */ 71 72 .text 73 .globl _start 74 75 _start: 76 /* 77 Configure RAM. 78 See: http://microchipdeveloper.com/32bit:mx-arch-exceptions-processor-initialization 79 */ 80 81 la $v0, BMXCON 82 li $v1, (1 << 6) /* BMXCON<6> = BMXWSDRM = 0 */ 83 sw $v1, CLR($v0) 84 85 /* Enable caching. */ 86 87 li $v0, CONFIG_CM_CACHABLE_NONCOHERENT 88 mtc0 $v0, CP0_CONFIG 89 nop 90 91 /* Get the RAM size. */ 92 93 la $v0, BMXDRMSZ 94 lw $v0, 0($v0) 95 96 /* Initialise the stack pointer. */ 97 98 li $v1, KSEG0_BASE 99 addu $sp, $v0, $v1 /* sp = KSEG0_BASE + RAM size */ 100 101 /* Initialise framebuffer. */ 102 103 jal init_framebuffer 104 nop 105 106 /* Initialise the globals pointer. */ 107 108 lui $gp, %hi(_GLOBAL_OFFSET_TABLE_) 109 ori $gp, $gp, %lo(_GLOBAL_OFFSET_TABLE_) 110 111 /* Set pins for output. */ 112 113 jal init_pins 114 nop 115 116 jal init_oc_pins 117 nop 118 119 /* Initialise the status register. */ 120 121 jal init_interrupts 122 nop 123 124 /* Initialise timer. */ 125 126 jal init_timer2 127 nop 128 129 /* Initialise PMP. */ 130 131 jal init_pmp 132 nop 133 134 /* Initialise DMA. */ 135 136 jal init_dma 137 nop 138 139 /* Initialise OC3 and OC2. */ 140 141 jal init_oc 142 nop 143 144 /* Enable interrupts and loop. */ 145 146 jal enable_interrupts 147 nop 148 149 jal handle_error_level 150 nop 151 152 /* Set initial sync conditions. */ 153 154 la $t0, PORTB 155 li $t1, (1 << 5) /* PORTB<5> = RB5 */ 156 sw $t1, SET($t0) 157 158 la $t0, PORTB 159 li $t1, (1 << 10) /* PORTB<10> = RB10 */ 160 sw $t1, SET($t0) 161 162 /* Main program. */ 163 164 li $a1, 5000000 /* counter = 5000000 */ 165 166 /* Initialise the display state. */ 167 168 li $s0, 0 /* line counter */ 169 la $s1, vbp_active /* current event */ 170 move $s2, $zero /* line address */ 171 172 /* Monitoring loop. */ 173 loop: 174 addiu $a1, $a1, -1 /* counter -= 1 */ 175 bnez $a1, loop /* until counter == 0 */ 176 nop 177 178 li $a1, 5000000 /* counter = 5000000 */ 179 180 la $t0, PORTB 181 li $t1, (1 << 11) /* PORTB<11> = RB11 */ 182 sw $t1, INV($t0) 183 184 _next: 185 j loop 186 nop 187 188 189 190 init_pins: 191 /* DEVCFG0<2> needs setting to 0 before the program is run. */ 192 193 la $v0, CFGCON 194 li $v1, (1 << 3) /* CFGCON<3> = JTAGEN = 0 */ 195 sw $v1, CLR($v0) 196 197 init_outputs: 198 /* Remove analogue features from pins. */ 199 200 la $v0, ANSELA 201 sw $zero, 0($v0) /* ANSELA = 0 */ 202 la $v0, ANSELB 203 sw $zero, 0($v0) /* ANSELB = 0 */ 204 205 la $v0, TRISA 206 sw $zero, 0($v0) 207 la $v0, TRISB 208 sw $zero, 0($v0) 209 210 la $v0, PORTA 211 sw $zero, 0($v0) 212 la $v0, PORTB 213 sw $zero, 0($v0) 214 215 jr $ra 216 nop 217 218 219 220 /* Initialisation routines. */ 221 222 init_timer2: 223 224 /* Initialise Timer2 interrupt. */ 225 226 la $v0, T2CON 227 sw $zero, 0($v0) /* T2CON = 0 */ 228 nop 229 230 la $v0, TMR2 231 sw $zero, 0($v0) /* TMR2 = 0 */ 232 233 la $v0, PR2 234 li $v1, HFREQ_LIMIT 235 sw $v1, 0($v0) /* PR2 = HFREQ_LIMIT */ 236 237 /* Initialise Timer2 interrupt. */ 238 239 la $v0, IFS0 240 li $v1, (1 << 9) 241 sw $v1, CLR($v0) /* T2IF = 0 */ 242 243 la $v0, IPC2 244 li $v1, 0b11111 245 sw $v1, CLR($v0) /* T2IP, T2IS = 0 */ 246 247 la $v0, IPC2 248 li $v1, 0b11111 249 sw $v1, SET($v0) /* T2IP = 7; T2IS = 3 */ 250 251 la $v0, IEC0 252 li $v1, (1 << 9) 253 sw $v1, SET($v0) /* T2IE = 1 */ 254 255 /* Start timer. */ 256 257 la $v0, T2CON 258 li $v1, (1 << 15) 259 sw $v1, SET($v0) /* ON = 1 */ 260 261 jr $ra 262 nop 263 264 265 266 /* 267 Output compare initialisation. 268 269 Timer2 will be used to trigger two events using OC3: one initiating the hsync 270 pulse, and one terminating the pulse. The pulse should appear after the line 271 data has been transferred using DMA, but this is achieved by just choosing 272 suitable start and end values. 273 274 Using OC2, Timer 2 triggers a level shifting event and OC2 is reconfigured to 275 reverse the level at a later point. 276 */ 277 278 init_oc: 279 /* Disable OC3 interrupts. */ 280 281 la $v0, IEC0 282 li $v1, (1 << 17) /* IEC0<17> = OC3IE = 0 */ 283 sw $v1, CLR($v0) 284 285 la $v0, IFS0 286 li $v1, (1 << 17) /* IFS0<17> = OC3IF = 0 */ 287 sw $v1, CLR($v0) 288 289 /* Initialise OC3. */ 290 291 la $v0, OC3CON 292 li $v1, 0b101 /* OC3CON<2:0> = OCM<2:0> = 101 (dual compare, continuous pulse) */ 293 sw $v1, 0($v0) 294 295 /* Pulse start and end. */ 296 297 la $v0, OC3R 298 li $v1, HSYNC_END /* HSYNC_START for positive polarity */ 299 sw $v1, 0($v0) 300 301 la $v0, OC3RS 302 li $v1, HSYNC_START /* HSYNC_END for positive polarity */ 303 sw $v1, 0($v0) 304 305 /* OC3 is enabled. */ 306 307 la $v0, OC3CON 308 li $v1, (1 << 15) 309 sw $v1, SET($v0) 310 311 /* Disable OC2 interrupts. */ 312 313 la $v0, IEC0 314 li $v1, (1 << 12) /* IEC0<12> = OC2IE = 0 */ 315 sw $v1, CLR($v0) 316 317 la $v0, IFS0 318 li $v1, (1 << 12) /* IFS0<12> = OC2IF = 0 */ 319 sw $v1, CLR($v0) 320 321 /* Initialise OC2. */ 322 323 la $v0, OC2CON 324 li $v1, 0b010 /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 325 sw $v1, 0($v0) 326 327 /* Set pulse position. */ 328 329 la $v0, OC2R 330 sw $zero, 0($v0) 331 332 /* Enable OC2 later. */ 333 334 jr $ra 335 nop 336 337 338 init_oc_pins: 339 /* Unlock the configuration register bits. */ 340 341 la $v0, SYSKEY 342 sw $zero, 0($v0) 343 li $v1, 0xAA996655 344 sw $v1, 0($v0) 345 li $v1, 0x556699AA 346 sw $v1, 0($v0) 347 348 la $v0, CFGCON 349 lw $t8, 0($v0) 350 li $v1, (1 << 13) /* IOLOCK = 0 */ 351 sw $v1, CLR($v0) 352 353 /* Map OC3 to RPB10. */ 354 355 la $v0, RPB10R 356 li $v1, 0b0101 /* RPB10R<3:0> = 0101 (OC3) */ 357 sw $v1, 0($v0) 358 359 /* Map OC2 to RPB5. */ 360 361 la $v0, RPB5R 362 li $v1, 0b0101 /* RPB5R<3:0> = 0101 (OC2) */ 363 sw $v1, 0($v0) 364 365 la $v0, CFGCON 366 sw $t8, 0($v0) 367 368 /* Lock the oscillator control register again. */ 369 370 la $v0, SYSKEY 371 li $v1, 0x33333333 372 sw $v1, 0($v0) 373 374 jr $ra 375 nop 376 377 378 379 /* Parallel Master Port initialisation. */ 380 381 init_pmp: 382 /* Disable PMP interrupts. */ 383 384 la $v0, IEC1 385 li $v1, (1 << 16) /* IEC1<16> = PMPIE = 0 */ 386 sw $v1, CLR($v0) 387 388 /* Initialise PMP. 389 390 PMCON<12:11> = ADDRMUX<1:0> = 0; demultiplexed address and data 391 PMCON<9> = PTWREN<0> = 0; no write pin 392 PMCON<8> = PTRDEN<0> = 0; no read pin 393 PMCON<7:6> = CSF<1:0> = 0; no chip select pins 394 */ 395 396 la $v0, PMCON 397 sw $zero, 0($v0) 398 399 /* 400 PMMODE<14:13> = IRQM<1:0> = 1; interrupt after every read/write 401 PMMODE<12:11> = INCM<1:0> = 0; no increment on every read/write 402 PMMODE<10> = MODE16<0> = 0; 8-bit transfers 403 PMMODE<9:8> = MODE<1:0> = 10; master mode 2 404 PMMODE<5:2> = WAITM<3:0> = 00; single cycle read/write, no chip select wait cycles 405 */ 406 407 la $v0, PMMODE 408 li $v1, 0x2200 409 sw $v1, 0($v0) 410 411 /* Free non-essential pins for general I/O. */ 412 413 la $v0, PMAEN 414 sw $zero, 0($v0) 415 416 la $v0, PMADDR 417 sw $zero, 0($v0) 418 419 la $v0, IFS1 420 li $v1, (3 << 16) /* IFS1<17:16> = PMPEIF, PMPIF = 0 */ 421 sw $v1, CLR($v0) 422 423 /* Start PMP mode. */ 424 425 la $v0, PMCON 426 li $v1, (1 << 15) /* PMCON<15> = ON = 1 */ 427 sw $v1, SET($v0) 428 429 jr $ra 430 nop 431 432 433 434 /* 435 Direct Memory Access initialisation. 436 437 Write 160 pixels to the PMP for the line data. This is initiated by a timer 438 interrupt. Upon completion of the transfer, a DMA interrupt initiates the 439 address update routine, changing the source address of the DMA channel. 440 */ 441 442 init_dma: 443 /* Disable DMA interrupts. */ 444 445 la $v0, IEC1 446 li $v1, (1 << 28) /* IEC1<28> = DMA0IE = 0 */ 447 sw $v1, CLR($v0) 448 449 /* Clear DMA interrupt flags. */ 450 451 la $v0, IFS1 452 li $v1, (1 << 28) /* IFS1<28> = DMA0IF = 0 */ 453 sw $v1, CLR($v0) 454 455 /* Enable DMA. */ 456 457 la $v0, DMACON 458 li $v1, (1 << 15) 459 sw $v1, SET($v0) 460 461 /* 462 Initialise a line channel. 463 The line channel will be channel 0 (x = 0). 464 465 Once the hsync channel has completed a transfer, the line channel 466 transfer is initiated. 467 468 Specify a priority of 3: 469 DCHxCON<1:0> = CHPRI<1:0> = 3 470 471 Auto-enable the channels: 472 DCHxCON<4> = CHAEN = 1 473 */ 474 475 la $v0, DCH0CON 476 li $v1, 0b10011 477 sw $v1, 0($v0) 478 479 /* 480 Initiate channel transfers when the initiating interrupt condition 481 occurs: 482 DCHxECON<15:8> = CHSIRQ<7:0> = timer 2 interrupt 483 */ 484 485 la $v0, DCH0ECON 486 li $v1, (9 << 8) | (1 << 4) 487 sw $v1, 0($v0) 488 489 /* 490 The line channel has a cell size of 160 bytes: 491 DCHxCSIZ<15:0> = CHCSIZ<15:0> = LINE_LENGTH 492 */ 493 494 la $v0, DCH0CSIZ 495 li $v1, LINE_LENGTH 496 sw $v1, 0($v0) 497 498 /* 499 Each source has a size identical to the cell size: 500 DCHxSSIZ<15:0> = CHSSIZ<15:0> = n 501 */ 502 503 la $v0, DCH0SSIZ 504 li $v1, LINE_LENGTH 505 sw $v1, 0($v0) 506 507 /* 508 The source address is the physical address of the line data: 509 DCHxSSA = line data physical address 510 */ 511 512 la $v0, DCH0SSA 513 sw $zero, 0($v0) 514 515 /* 516 Each destination has a size of 1 byte: 517 DCHxDSIZ<15:0> = CHDSIZ<15:0> = 1 518 */ 519 520 la $v0, DCH0DSIZ 521 li $v1, 1 522 sw $v1, 0($v0) 523 524 /* 525 For the line channel, the destination address is the physical address of 526 PMDIN: DCHxDSA = physical(PMDIN) 527 */ 528 529 la $v0, DCH0DSA 530 li $v1, PMDIN 531 li $t8, KSEG1_BASE 532 subu $v1, $v1, $t8 533 sw $v1, 0($v0) 534 535 /* 536 Use the block transfer completion interrupt to indicate when the source 537 address can be updated. 538 */ 539 540 la $v0, DCH0INT 541 li $v1, (1 << 19) /* CHBCIE = 1 */ 542 sw $v1, 0($v0) 543 544 /* Enable interrupt for address updating. */ 545 546 la $v0, IPC10 547 li $v1, 0b11111 /* DMA0IP, DMA0IS = 0 */ 548 sw $v1, CLR($v0) 549 550 la $v0, IPC10 551 li $v1, 0b11111 /* DMA0IP = 7, DMA0IS = 3 */ 552 sw $v1, SET($v0) 553 554 la $v0, IEC1 555 li $v1, (1 << 28) /* IEC1<28> = DMA0IE = 1 */ 556 sw $v1, SET($v0) 557 558 /* Enable channels. */ 559 560 la $v0, DCH0CON 561 li $v1, 0b10000000 562 sw $v1, SET($v0) 563 564 jr $ra 565 nop 566 567 568 569 /* Framebuffer initialisation. */ 570 571 init_framebuffer: 572 li $v0, KSEG0_BASE 573 li $t8, SCREEN_SIZE 574 li $v1, 0xff031ce0 575 576 _init_fb_loop: 577 sw $v1, 0($v0) 578 addiu $v0, $v0, 4 579 addiu $t8, $t8, -4 580 bnez $t8, _init_fb_loop 581 nop 582 583 jr $ra 584 nop 585 586 587 588 /* Utilities. */ 589 590 handle_error_level: 591 mfc0 $t3, CP0_STATUS 592 li $t4, ~(STATUS_ERL | STATUS_EXL) 593 and $t3, $t3, $t4 594 mtc0 $t3, CP0_STATUS 595 jr $ra 596 nop 597 598 enable_interrupts: 599 mfc0 $t3, CP0_STATUS 600 li $t4, ~STATUS_IRQ /* Clear interrupt priority bits. */ 601 and $t3, $t3, $t4 602 li $t4, ~STATUS_BEV /* CP0_STATUS &= ~STATUS_BEV (use non-bootloader vectors) */ 603 and $t3, $t3, $t4 604 ori $t3, $t3, STATUS_IE 605 mtc0 $t3, CP0_STATUS 606 jr $ra 607 nop 608 609 init_interrupts: 610 mfc0 $t3, CP0_DEBUG 611 li $t4, ~DEBUG_DM 612 and $t3, $t3, $t4 613 mtc0 $t3, CP0_DEBUG 614 615 mfc0 $t3, CP0_STATUS 616 li $t4, STATUS_BEV 617 or $t3, $t3, $t4 618 mtc0 $t3, CP0_STATUS 619 620 la $t3, exception_handler 621 mtc0 $t3, CP0_EBASE /* EBASE = exception_handler */ 622 623 li $t3, 0x20 /* Must be non-zero or the CPU gets upset */ 624 mtc0 $t3, CP0_INTCTL 625 626 li $t3, CAUSE_IV /* IV = 1 (use EBASE+0x200 for interrupts) */ 627 mtc0 $t3, CP0_CAUSE 628 629 /* Multiple vector... 630 la $t3, INTCON 631 li $t4, (1 << 12) 632 sw $t4, SET($t3) */ 633 634 jr $ra 635 nop 636 637 638 639 /* Exception servicing. */ 640 641 .section .flash, "a" 642 643 /* Exception servicing. */ 644 645 exception_handler: 646 li $t8, 2500000 647 exc_loop: 648 addiu $t8, $t8, -1 649 bnez $t8, exc_loop 650 nop 651 la $v0, PORTB 652 li $v1, (1 << 11) /* PORTB<11> = RB11 */ 653 sw $v1, INV($v0) 654 j exception_handler 655 nop 656 657 658 659 /* Interrupt servicing. */ 660 661 .org 0x200 662 663 interrupt_handler: 664 665 /* Check for a timer interrupt condition. */ 666 667 la $v0, IFS0 668 lw $v1, 0($v0) 669 andi $v1, $v1, (1 << 9) /* T2IF */ 670 beqz $v1, irq_dma 671 nop 672 673 /* Increment the line counter. */ 674 675 addiu $s0, $s0, 1 676 677 /* Jump to the event handler. */ 678 679 jalr $s1 680 nop 681 682 irq_clear_timer: 683 684 /* Clear the timer interrupt condition. */ 685 686 la $v0, IFS0 687 li $v1, (1 << 9) /* IFS0<9> = T2IF = 0 */ 688 sw $v1, CLR($v0) 689 690 irq_dma: 691 692 /* Check for a DMA interrupt condition. */ 693 694 la $v0, IFS1 695 lw $v1, 0($v0) 696 li $t8, (1 << 28) /* DMA0IF */ 697 and $v1, $v1, $t8 698 beqz $v1, irq_exit 699 nop 700 701 /* Test the block transfer completion interrupt flag. */ 702 703 la $v0, DCH0INT 704 lw $v1, 0($v0) 705 andi $v1, $v1, (1 << 3) /* CHBCIF */ 706 beqz $v1, irq_clear_dma 707 nop 708 709 /* Clear the block transfer completion interrupt flag. */ 710 711 li $v1, (1 << 3) /* CHBCIF = 0 */ 712 sw $v1, CLR($v0) 713 714 /* 715 Update the line data address if the line counter (referring to the 716 next line) is even. 717 */ 718 719 andi $t8, $s0, 1 720 bnez $t8, irq_clear_dma 721 nop 722 723 /* Reference the next line and update the DMA source address. */ 724 725 addiu $s2, $s2, LINE_LENGTH 726 727 /* Test for wraparound. */ 728 729 li $t8, SCREEN_SIZE 730 sltu $t8, $s2, $t8 731 bnez $t8, irq_dma_update 732 nop 733 734 /* Reset the source address. */ 735 736 move $s2, $zero 737 738 irq_dma_update: 739 740 la $v0, DCH0SSA 741 sw $s2, 0($v0) 742 743 irq_clear_dma: 744 745 /* Clear the DMA interrupt condition. */ 746 747 la $v0, IFS1 748 li $v1, (1 << 28) /* IFS1<28> = DMA0IF = 0 */ 749 sw $v1, CLR($v0) 750 751 irq_exit: 752 eret 753 nop 754 755 756 757 /* Event routines. */ 758 759 /* The vertical back porch. */ 760 761 vbp_active: 762 /* Test for visible region. */ 763 764 sltiu $v0, $s0, VISIBLE_START 765 bnez $v0, _vbp_active_ret 766 nop 767 768 /* Start the visible region. */ 769 770 la $s1, visible_active 771 772 _vbp_active_ret: 773 jr $ra 774 nop 775 776 777 778 /* The visible region. */ 779 780 visible_active: 781 /* Test for front porch. */ 782 783 sltiu $v0, $s0, VFP_START 784 bnez $v0, _visible_active_ret 785 nop 786 787 /* Start the front porch region. */ 788 789 la $s1, vfp_active 790 791 _visible_active_ret: 792 jr $ra 793 nop 794 795 796 797 /* Within the vertical front porch. */ 798 799 vfp_active: 800 /* Test for vsync. */ 801 802 sltiu $v0, $s0, VSYNC_START 803 bnez $v0, _vfp_active_ret 804 nop 805 806 /* Start the vsync. */ 807 808 la $s1, vsync_active 809 810 /* Bring vsync low when the next line starts. */ 811 812 la $v0, OC2CON 813 li $v1, 0b010 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 010 (single compare, output driven low) */ 814 sw $v1, 0($v0) 815 816 _vfp_active_ret: 817 jr $ra 818 nop 819 820 821 822 /* The vsync period. */ 823 824 vsync_active: 825 /* Test for front porch. */ 826 827 sltiu $v0, $s0, VSYNC_END 828 bnez $v0, _vsync_active_ret 829 nop 830 831 /* Start the back porch. */ 832 833 move $s0, $zero 834 la $s1, vbp_active 835 836 /* Bring vsync high when the next line starts. */ 837 838 la $v0, OC2CON 839 li $v1, 0b001 | (1 << 15) /* OC2CON<2:0> = OCM<2:0> = 001 (single compare, output driven high) */ 840 sw $v1, 0($v0) 841 842 _vsync_active_ret: 843 jr $ra 844 nop