Skip to content
Snippets Groups Projects
  1. Dec 12, 2021
    • Geo Ster's avatar
      Add support for IOP timers and interrupts · af6560f5
      Geo Ster authored
      * After a while the IOP starts setting up timer 5 so we need to start
      implementing timer support. Timers are pretty simple actually. Each one
      has 3 32bit registers (we use 64bit registers to check for overflow), a
      count register that counts the number of cycles, the mode register which
      configures the timer and the target register which generates an interrupt
      when count == target. For now that's all we need.
      
      * In addition, interrupts are also implmented. These are a bit more
      complicated since they involve COP0, but not to difficult either. The IOP
      has 3 registers, I_MASK, I_STAT and I_CTRL. I_CTRL acts as a global
      enable/disable so it's pretty simple. I_STAT is a bit mask that states
      which interrupts are pending. I_MASK on the other hand has the ability
      to enable/disable specific interrupts. So to check if the interrupt will be
      executed we must do !I_CTRL && (I_MASK & I_STAT). All info can be found
      on ps2tek/nocash psx
      
      * On the EE side, a few new instructions are added to progress further.
      Now the EE starts setting up the GIF, which is quite exciting!
      
      * I think it's a good time to also elaborate on how we read structs. Instead
      of using switch statements I prefer pointers and struct because these
      generate a lot more compat code, even with compiler optimizations and eliminate
      the need for branches. This should not be of concern on normal applications but
      we are special ;). Most registers on the console are 32bit, so structs
      are cast to uint32_t* to access them. And since the offsets are always in bytes
      we must divide them by sizeof(uint32_t) = 4 (I prefer >> 2 since it's more efficient)
      
      * Some registers however are peculiar in a sense that a parts of them
      are located in completely different address ranges. This is bad because
      for example timer 0 and timer 3 have the same offset of 0 from their relative
      address ranges. To fix this, we introduce a variable called group that records
      which "group" the write/read is refering to, with some simple bit masks.
      The result is casted to bool which converts the result to 0 or 1. Then
      the expression "offset + group * <number>" is used to access registers.
      In the timer examples accessing timer 0 will give group 0 and offset 0
      so timer 0 will be accessed. With timer 3 though group will be 1 so
      0 + 1 * 3 = 3 will be accessed. This a convenient way to bypass branches.
      af6560f5
  2. Dec 10, 2021
    • Geo Ster's avatar
      Groundwork for IOP DMA implementation · 1cce9b6f
      Geo Ster authored
      * The DMA routine on the IOP works similarly to the PSX version with a
      few additions. There are 7 channels from the PSX and an additional 6 new
      PS2 exclusive ones. One the PSX, each channel has 3 registers used
      to configure and use it and 3 global registers.
      
      * The PS2 contains all the older DMA registers, but it add 6 more channels
      and duplicates the global registers (DPCR now has a counterpart called DPCR2)
      This is done because each global register can control up to 7 channels.
      An additional register on each channel (tadr) and 2 additional
      global registers have been added as well. For now we don't really
      care to implement them, only read/write to them.
      
      * For reading and writing to the registers structs are used to prevent
      the usage of switch and if statements.
      1cce9b6f
  3. Dec 04, 2021
    • Geo Ster's avatar
      Fix load instruction logging in IOP · 42172fa2
      Geo Ster authored
      * Due to load delay slots the target register doesn't get
      written immediately, so use the value instead to correctly
      display the loaded value in the logs
      42172fa2
  4. Dec 02, 2021
  5. Dec 01, 2021
    • Geo Ster's avatar
      Minor branch optimization · 1a3d77c9
      Geo Ster authored
      * On reads/writes it is important to check the address alignment before
      proceeding with the operation. However unalignment errors almost
      never happen in real world games, so let the compiler know that these
      branches are unlikely to happen to speed them up a bit.
      1a3d77c9
  6. Nov 30, 2021
    • Geo Ster's avatar
      Introducing the IOP · 1d16fdad
      Geo Ster authored
      * So after a week, it's finally here! The initial implementation of the
      IOP has been added to the emulator. You might wonder why did it take so
      long? This was mostly because I wanted to make the implementation as complete
      as possible and also test it to ensure it's bug free. So this is actually
      based on the MIPS R3000A interpreter I wrote last year for my PS1 emulator.
      So did I just copy the code and call it a day? Hell no, the code in that
      ancient project is awful, even if it works. So I completely rewrote the
      interpreter by using our modern techiniques of storing state. So rewriting the old
      code allowed me to test if it actually worked in that environment
      and could boot PSX games.
      
      * Due to this, the implementation is a bit more complete than the EE
      as it includes interrupt support. In addition we have to account for
      the fact that the IOP runs at 36.864MHz, in constrast to the EE which
      clocks at 295MHz. This maps approximatly to an 1/8 ratio, which means
      that 1 IOP instruction will run every 8 EE cycles. The current implementation
      of this is hacky and a bit inaccurate because some EE instructions
      can take more than 1 cycle to execute, but it's good enough for now
      (Play! assumes this as well and can boot 40%+ of games).
      
      * Because both the CPU emulators can share a lot of naming conventions,
      to avoid confusion each processor has been seperated into a namespace
      so we can always know which CPU we are refering to. Finally, for now
      reads/writes except for the BIOS and IOP RAM, haven't been implemented
      but will come soon.
      1d16fdad
  7. Nov 29, 2021
    • Geo Ster's avatar
      Simplify branch delay detection logic · 6355a7f8
      Geo Ster authored
      * Instead of having a global is_branch_delay that we must manage, instead
      we can just set the attribute of the next instruction since we have
      it ahead of time.
      
      * In addition fix an oopsie in the alignment detection logic in op_lw
      that caused an unwaranted exception and infinite loop
      6355a7f8
  8. Nov 17, 2021
    • Geo Ster's avatar
      Add exception support · 9145d821
      Geo Ster authored
      * This commit adds support for handling exceptions for our virtual
      MIPS CPU. The implementation is based on the provided document's chapter
      on Exception handling, and more specifically on the flowchart of level 1
      exceptions (Section 5.1.1). To check if the current instruction is in a delay
      slot we use a bool and cache it together with the currently executing instruction.
      
      * The current implementation is only partially complete, since it's missing level 2
      exception handling, but I reckon that those exceptions won't be needed
      for a long time. This acts more as a foundation for implementing interrupts
      which are 100% required for emulating even the most basic of systems.
      9145d821
    • Geo Ster's avatar
      Ignore unrecognized reads/writes · 327c71d0
      Geo Ster authored
      * Currently the BIOS only writes to scratchpad and some very few mysterious
      addresses that don't seem to do anything. However it is important to know
      when it will try to write to DMAC for example so we can implement it. So
      instead of writing anything and uncontrollably into a single large buffer
      let's make an if-else with all the known addresses and how to handle them.
      When the BIOS tries to write somewhere new we will be notified immediately.
      
      * Also rework the disassembly logger to use C FILE* since these are faster
      then std::ofstream. Normally I wouldn't care about this but in our usecase
      which is very performance sensitive, it makes a noticeable difference.
      327c71d0
  9. Nov 16, 2021
    • Geo Ster's avatar
      Progress further - solve infinite loop · 40e730b7
      Geo Ster authored
      * Add a few more instructions required to progress further into the BIOS
      execution
      
      * After that the BIOS writes something to 0xb000f410 and then enters
      a loop at 0x9fc417b0 that only exits when that address contains a value of 0.
      So unless that address magically changes value on its own, it's either an interrupt
      (higly unlikely since the INT regs aren't touched) or the IOP doing
      this (also impossible since the IOP and EE only communicate with DMA and
      have seperate memory regions). So my guess is that the BIOS expects that address
      to always be zero no matter the value written to it. Until I am proven wrong
      let's stick to that to exit that infinite loop and continue...
      40e730b7
    • Geo Ster's avatar
      Move ComponentManger to a seperate thread · d8a4a251
      Geo Ster authored
      * Currently the interperter performance was extermely slow
      due to the handling of window events from glfw blocking the execution
      of new instructions. Moving the execution to a seperate thread, results
      in massive performance improvements. However the current implementation
      is only temporary and will be modularized in the future.
      d8a4a251
    • Geo Ster's avatar
      Implement reads/writes MCH_RICM/MCH_DRD · 45db21fd
      Geo Ster authored
      * The BIOS tries to write to 0x1000f430/0x1000f440 which contain the
      registers MCH_RICM/MCH_DRD. Saldy these registers are quite undocumented.
      So the writing logic has been taken from PCSX2:
      https://github.com/PCSX2/pcsx2/blob/master/pcsx2/HwWrite.cpp#L237
      Forgive me, for I have sinned.
      45db21fd
    • Geo Ster's avatar
      Fix bugged scratchpad reads/writes · 688bc87f
      Geo Ster authored
      * Looking at the console output introduced in the previous commit, it
      was obvious that it was very wrong. Booting the same BIOS file with PCSX2
      reports: Initialize memory (rev:3.17, ctm:393Mhz, cpuclk:295Mhz )
      
      * It turns out that the problem wasn't with the CPU, but the string printing
      function. The BIOS writes the numbers it calculates to the scratchpad and
      the print function reads them from there. But there were 2 issues with the
      current implementation
      
      1. addr & 0x3FFC is ignoring the last 2 bits which makes 8bit writes/reads
      broken.
      
      2. Always casting to uint32_t* instead of the type provided T* results in
      byte write overriding whole 32bit sections of the scratchpad, destroying
      critical data.
      688bc87f
    • Geo Ster's avatar
      Wrap log funtion into a macro and implement BIOS console · c4c3a1ff
      Geo Ster authored
      * Since log output is getting very large, it's common to have to wait
      5+ minutes before any unknow instruction is encoutered. Printing to the
      console actually takes a lot of time and slows down interpretation
      significantly. Right now we don't care, since we just want to boot the BIOS
      but let's have an option to disable all the log messages if we want.
      
      * In addition record all writes to 0xb000f180 which is the BIOS console
      output address, so we can have some output, which will be very useful
      c4c3a1ff
  10. Nov 15, 2021
    • Geo Ster's avatar
      Add more shift (SRA/SLL) instructions · 49cabc82
      Geo Ster authored
      * Now the BIOS enters another infinite loop. However that seems to be
      normal, as it's waiting for COP0_REG[9], the timer which we haven't
      implemented yet. I think it's also time to add support for interrupts as
      well since these go hand in hand with timers
      49cabc82
    • Geo Ster's avatar
      Treat registers as signed in branch instructions with comparisons · eee0af83
      Geo Ster authored
      * The documentation doesn't state this, but it's necessary. On the loop
      at 0xbfc43140 the register s3 is loaded with 0x27 and is used as the counter
      in a for loop. However because its value wasn't treated as signed so it looked
      more like 0xffffffffffffff27, which is very large, making the loop run forever.
      Fix this by treating registers as signed where needed
      eee0af83
  11. Nov 14, 2021
    • Geo Ster's avatar
      Fix bug in the BEQ/BEQL/BNEL instructions · 9ec5b188
      Geo Ster authored
      * Seems like branches really do love having bugs in them ;)
      The bug was noticed when the BEQ instruction was provided 0xffd1 as the offset.
      Decompiling with ghidra revealed that the offset was -0xbc or -188 as signed
      but with this bug the value would be 261956 which completely broke
      the program. Fix this by first casting to int16_t to let the
      compiler know that we are giving it a 16bit signed int and then convert
      it to int32_t
      
      * In addition make stores/loads bold so I can notice them better, as
      log output is starting to incrase exponentially
      9ec5b188
    • Geo Ster's avatar
      Minor fixes · eeaa0b20
      Geo Ster authored
      * Add more instructions, now the BIOS starts exeuting for longer
      without interruption yay!
      * Move some logging messages before the instruction, to avoid printing
      wrong values in case a register is modified with itself.
      eeaa0b20
    • Geo Ster's avatar
      Implement first FPU instruction · 9444ba92
      Geo Ster authored
      * Since we have encountered our first FPU register, add the 32 floating
      point registers to the CPU.
      
      * In addition solve a small bug in the JAL instruction related to the
      return link address. See previous commit for details
      9444ba92
    • Geo Ster's avatar
      Fix bug in jump instructions · 2c622b24
      Geo Ster authored
      * As stated in earlier commits we prefetch next instruction before
      the current one gets executed to guarantee that we have it available
      in case a branch instruction changes the PC. So a typical fetch cycle
      for a branch instruction would look like:
      
      /* Cycle 1. */
      instr = <something>
      next_instr = read(PC) -> jump
      PC += 4 (now it points to the branch delay)
      
      /* Cycle 2. */
      instr = jump
      next_instr = read(PC) -> branch delay
      PC += 4 (now it points to the instruction AFTER the delay slot)
      
      <execute branch>
      
      So if a branch uses offsets instead of hard coding the PC, it will point
      to the wrong address since it expects to have the PC pointing to the branch
      delay instruction. To fix this, subtrack 4 from the pc.
      2c622b24
    • Geo Ster's avatar
      Move header files to src dir · 9969bd6f
      Geo Ster authored
      * Having the header files in a seperate dir is only useful when developing
      libraries that are meant to be used by other programs. In our case though
      it makes the file structure more tedious so gather everything in one place.
      9969bd6f
    • Geo Ster's avatar
      Migrate to fmt for logging · 035008f7
      Geo Ster authored
      * std::cout is not fit for our usecase, which is format heavy output.
      This results in a lot of code bloat from switching between std::dec and
      std::hex. Switching to fmt yields significantly cleaner code.
      035008f7
    • Geo Ster's avatar
      Improve PC handling · 40b83f7b
      Geo Ster authored
      * Currently the way PC was logged was kinda broken, especially on branch
      delay slots where it would show the jump address instead. This doesn't
      affect the functionality but makes debugging a bit harder so to fix this,
      cache the PC together with the instruction being loaded to always have
      the correct address.
      40b83f7b
    • Geo Ster's avatar
      Continue going through the BIOS · 95f02f67
      Geo Ster authored
      * Implement more instructions as usual
      * Rename instr.doubleword to instr.dword for more compat code
      * Add the skip branch delay slots feature for beq* instructions
      95f02f67
  12. Nov 13, 2021
    • Geo Ster's avatar
      Remove dependency on int128 · 5cafeb61
      Geo Ster authored
      * This is only supported on GCC/Clang making compilation fail on
      MSVC. In addition we don't really need this since accessing the
      entire 128bit register is very rare and can be emulated by setting
      each of 64bit parts seperately.
      5cafeb61
    • Geo Ster's avatar
      Implement more instructions in the CPU · 2daed618
      Geo Ster authored
      * Nothing special really, just adding more instructions and fixes minor
      bugs to progress further
      2daed618
  13. Nov 04, 2021
    • Geo Ster's avatar
      Add scratchpad handler · 49a61fb1
      Geo Ster authored
      * Currently the BIOS tries to write something to scratchpad and it fails
      because its address is not our currently supported range. So add a new buffer
      for the 16KB scratchpad used by the CPU and add a function to use it if the
      address is in the correct range.
      
      * Also shorten some function names and change some array to C-style because we
      like to work with pointers and STL doesn't like that.
      49a61fb1
    • Geo Ster's avatar
      Implement MADD1/MTC0/JALR/SD and rewrite COP0 decoding · e2559bb5
      Geo Ster authored
      * COP0 instructions sadly have way too many encoding to use a single template one them,
      so rewrite the decoder to manually extract the required bits for each instruction. This
      fixes a bug where the MTC0 instruction was mistaken for MFC0.
      
      * Implement more instructions so we can execute more of the BIOS. In addition fix
      an oopsie in the ORI instruction which fixes some bugged memory writes. As of now the
      BIOS tries to write address 0x70003fe0 which maps to the scratchpad memory (assuming no further
      logic errors are present). Need to investigate why this is and how to emulate it before
      continuing...
      e2559bb5
  14. Nov 03, 2021
    • Geo Ster's avatar
      Set correct processor prid · fd3f2c31
      Geo Ster authored
      * As this very useful document describes [1]
      the BIOS first checks the prid of COP0 to decide
      whether to execute the IOP or EE boot sequence.
      Without this the BIOS doesn't execute the code we want.
      The code sequence that determines this is added below as
      suedo assembly
      
      MFC0: GPR[26] = COP0_REG[15] /* Load cop0 prid to GPR 26 */
      SLTI: GPR[1] = GPR[26] < 89 /* Check if its value is less than 0x59 and store the bool in GPR 1 */
      BNE: if GPR[0] != GPR[1] then pc += 20 /* If the comparison is true then jump +20 to IOP */
      
      [1] https://rust-console.github.io/ps2-bios-book/print.html
      [2] https://psi-rockin.github.io/ps2tek/#biosbootprocess
      fd3f2c31
    • Geo Ster's avatar
      Implement ORI/ADDI/LQ · ebf1986d
      Geo Ster authored
      In addition:
      * Correct register notation (word refers to a 32bit quantity not 16bit)
      * Simplify sign extension casing
      * Add more useful logging
      ebf1986d
    • Geo Ster's avatar
      Handle branch delay slots · 2f7596b8
      Geo Ster authored
      * MIPS has an architectural feature, where instead of flushing the
      pipeline when executing a branch instruction, it goes ahead and executes
      the instruction following the branch as well. Flushing the pipeline
      is costly and is the cause of those complex branch predictors on
      modern CPUs that try to guess when a branch will be taken.
      
      * To properly emulate this behaviour we must act how the pipeline acts. We
      will always have 2 instructions loaded the current one and the next one. This way
      we can load the instruction after the branch even though the pc changes.
      2f7596b8
    • Geo Ster's avatar
      Implement first instructions mfc0/sw/bne/sll · 0e09c3a1
      Geo Ster authored
      * The instruction decoding was based on the handy table in ps2tek [1]
      while the instructions themselves were written based on the document
      added.
      
      * The implementation makes heavy use of bitfields and unions in C++ to
      make accessing different bits/sections of registers easier and more
      intuitive. You may also notice the frequent casting (uint32_t)(int16_t)
      which might seem useless but is there to force the compiler to generate
      instructions to sign extend the offsets.
      
      [1] https://psi-rockin.github.io/ps2tek/#eeinstructiondecoding
      0e09c3a1
  15. Oct 31, 2021
    • Geo Ster's avatar
      Implement read/write operations and KUSEG regions · 8653f76f
      Geo Ster authored
      * Now that the BIOS is loaded we can start executing it!
      The starting address the EE uses is 0xbfc00000 which maps
      to KUSEG1. Since all KUSEG regions except KUSEG2 are mirrors
      of each other we only need to translate the address to the
      KUSEG appropriate.
      
      * The functional differences between KUSEG0/1 are minimal and
      very niche so I won't bother emulating them now. Address wise
      we can notice that the only difference between addresses is the
      most significant half byte. By using that byte as an index in
      a mask table we can define an appropriate mask for each KUSEG
      address. Idea taken from a very handy PSX document I discovered
      last year [1]
      
      [1] https://svkt.org/~simias/guide.pdf (43)
      8653f76f
    • Geo Ster's avatar
      Add initial memory map · a54398cc
      Geo Ster authored
      * The ps2tek documentation states the memory map clearly [1].
      It seems to have a similar architecture with the PSX, where
      the main memory map (KUSEG0) is mirrored in multiple regions
      (KUSEG1/KUSEG2) with different access patterns for each region.
      For now we don't have to emulate all of them, just the main
      memory map.
      
      * Allocate the entire 512MB memory into an array and make a convenient
      struct to abstract memory range operations.
      
      [1] https://psi-rockin.github.io/ps2tek/#memorymap
      a54398cc
    • Geo Ster's avatar
      Add initial EE/Bus implementations · 2ad0640f
      Geo Ster authored
      * This commit adds a most basic CPU class that acts as a template
      which we will slowly build.
      
      * The architecture is pretty simple; the ComponentManager will create all
      the seperate components (EE, VP, IOP, GS etc) as unique_ptr's since
      it owns them and only it has access to them. All the other components
      must pass through the manager to read/write data to memory.
      To achieve this they are given a pointer to the ComponentManger in their constructor.
      
      * For now the CPU directly accesses the bios which shouldn't
      happen but will be fixed eventually when I implement generic
      read/writes. The goal is to start implementing the CPU as fast as
      possible in order to get to the GPU/VPU's and display something!
      2ad0640f
    • Geo Ster's avatar
      Initial commit · 9c39208d
      Geo Ster authored
      * This is the beginning of a surely arduous journey of semi-correctly emulating
      the PS2 the flagship console from Sony in 2001. The console was chosen
      for it's impressive performance at the time, relatively simple MIPS architecture
      compared to the PowerPC (Gamecube) and x86 (Xbox) competitors at the time, and because
      I own one since I wouldn't want to be caught doing piracy on an open source
      competition...
      
      The PS2 also has a myriad of resources available including comprehensive CPU documentation
      for it's MIPS ISA which will be used in the development of this emulator. Any sources that I use, will be referenced
      in the coresponding commits for the judges to look at. For development hardware documentation and info from real emulators will be used
      (I'll try to avoid using code from other projects as much as possible though).
      I've also done a PS1 emulator in the past so the minor similarities in architecture
      will help speed this process up a little.
      
      For now this is just a window with glfw and a ready opengl context.
      I hope it will be able to boot the PS2 soon enough though...
      9c39208d
Loading