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