Jump to content
IGNORED

Requesting help in improving TIA emulation in Stella


stephena

Recommended Posts

To be accurate, I think the emulation should simulate all of the TIA's inputs and outputs. I see there's been mention of the graphics objects, but don't forget the blanking signal, sync signal, lum signals, and color signal, as well as the two audio signals, plus the ready signal, etc.

For now I will only refactor the graphic objects. I suppose defining more objects to handle other stuff should be no problem, but besides the two audio channels, I cannot see how the other signals could be seen as an object.

 

Though maybe some of them (lum and color) could become objects being used by the graphic objects? If they are independent enough... :ponder:

Edited by Thomas Jentzsch
Link to comment
Share on other sites

Hm, looks like I have done too much Java over the last years. Are inner classes in C++ really that useless as it seems to me?

 

I wanted to define the graphic objects as inner classes of the TIA class, so that they can have access to all TIA members. This is a very common pattern in Java.

 

But C++ doesn't seem to allow that. Here I need an object reference to the TIA class and can only access the public members of TIA. This could be done with any other, non-inner class too. So what are inner classes meant for?

 

And much more important, how can I define the graphic object classes, so that they have easy access to TIA members?

 

All I can see is:

  1. provide getters for almost all members in TIA and store a reference to TIA in the graphic object classes
  2. provide large parameter lists to the graphic object methods

In Java this would be sooo much easier. So I hope I am missing something. Help!?

Link to comment
Share on other sites

As part of my job, I teach introductory and intermediate Java programming. And I've seen this issue before. And yes, it is a shortcoming of C++ vs. Java. In C++, an inner class may as well be a separate class in a separate file. If you want to (sort of) simulate how it works in Java, you need to do two things:

  1. Pass a reference to the parent (TIA) into each 'inner' class, preferably at the c'tor so it can be stored as a reference in the graphics object classes too.
     
  2. Make all graphic object classes 'friends' of the TIA, so they can access private members without needing to provide getters and setters for everything (not always necessary).

Here's an example:

 

 

#include <string>
#include <iostream>
using namespace std;
class TIA
{
 private:
   class PlayerObject
   {
  public:
    PlayerObject(TIA& tia) : _tia(tia)
    {
	  cout << "PlayerObject c'tor" << endl;
    }
    virtual ~PlayerObject() { }
    string getState()
    {
	  // Notice how we can access _s from TIA, even though it's private?
	  // And we didn't need getter/setter or friend declaration.
	  return "private TIA variable: " + _tia._s;
    }
  private:
    TIA& _tia;
    // All your PlayerObject state ...
   };
 public:
   TIA(const string& s) : _s(s)
   {
  cout << "TIA::c'tor: " << _s << endl;
  _player1 = new PlayerObject(*this);  // this is where the association to TIA is made
   }
   virtual ~TIA() { }
   void updateState()
   {
  // This is where TIA can call methods of PlayerObject
  cout << _player1->getState() << endl;
   }
 private:
   string _s;
   PlayerObject* _player1;
};
int main(int ac, char* av[])
{
 TIA tia("hello");
 tia.updateState();
 return 0;
}

Link to comment
Share on other sites

Also, see http://stackoverflow...ivate-variables . This seems to imply that the friend statement isn't needed, but I think it still is when you want to access private stuff.

 

EDIT: The new code example above is very basic. Realistically, it would be broken up better than this, with the definitions in a header file, and the implementation in the source file. This is probably different from what you're used to in Java, where everything goes in one class.

 

EDIT2: The reason I needed friend classes in other parts of the code is that the classes aren't actually inner classes, and are defined in separate files.

Link to comment
Share on other sites

Thanks, I think I already was close (missed the const for the TIA reference) .

 

Often get something compiling and then I realize that I need a minor change. And then I am having a hard time to get it working again.

 

BTW: Why is there no "class TIA {...}" around TIA in the TIA.cxx like in .hxx? This prefixing is pretty annoying and creates unwanted errors. I tried to add it, but it didn't work. And all examples I found seem to indicate that there is no way around. True?

Link to comment
Share on other sites

Thanks, I think I already was close (missed the const for the TIA reference) .

 

Often get something compiling and then I realize that I need a minor change. And then I am having a hard time to get it working again.

 

BTW: Why is there no "class TIA {...}" around TIA in the TIA.cxx like in .hxx? This prefixing is pretty annoying and creates unwanted errors. I tried to add it, but it didn't work. And all examples I found seem to indicate that there is no way around. True?

 

That's just the nature of C++ classes. It's what I was referring to earlier with the definitions (often with empty methods) in the header file, and the implementation in the source file. Java doesn't have this concept; everything is defined in the same place. Each approach has pros and cons. I'd prefer to do it the C++ way, with the code in the source file whenever possible. The main benefit is that the entire codebase (ie, everything that the header file depends on) doesn't have to be recompiled with a change to the source file. IOW, changes to header files cascade everywhere and increase compile times.

 

To answer your actual question (:) ), no, there's no way around this namespacing. It's how you reference the header files from the source files. Remember, in C++, compiling can basically suck every file/class into a single file and compile from there. So there needs to be a way to differentiate by names. You get used to it after a while.

Link to comment
Share on other sites

To answer your actual question ( :) ), no, there's no way around this namespacing. It's how you reference the header files from the source files. Remember, in C++, compiling can basically suck every file/class into a single file and compile from there. So there needs to be a way to differentiate by names. You get used to it after a while.

With so many language elements, why can't C++ have something like setting a class prefix until it is disabled again? :)

 

Sure, everything has its pros and cons. But sometimes I wish they would learn more from each other. E.g. it took ages until Java introduced annotations. And I am still sometimes missing conditional defines and macros.

 

I suppose my code will look like Java style forced into C++. :D

Edited by Thomas Jentzsch
Link to comment
Share on other sites

With so many language elements, why can't C++ have something like setting a class prefix until it is disabled again? :)

 

 

it does: when you define stuff inside the class :) The problem you're seeing is when things are separated, which really has no equivalent concept in Java. Actually, I've always found that to be a drawback of Java, since you can never truly separate interface and implementation. Anyway, probably better to leave the language wars to another thread :)

 

Sure, everything has its pros and cons. But sometimes I wish they would learn more from each other. E.g. it took ages until Java introduced annotations. And I am still sometimes missing conditional defines and macros.

 

I suppose my code will look like Java style forced into C++. :D

 

Another huge thing to be mindful of is passing stuff around in parameters. If you pass an object itself, C++ will use a copy constructor, which is essentially a fancy way of saying it will do a deep copy of an object. In java, you're passing around object addresses so you never worry about this. That's why in a lot of places in the code, you'll see something passed as (const ObjectType& obj) and not just (ObjectType obj). The former emulates what Java does, the latter can be quite slow and wasteful.

 

Also, there's no garbage collection, so be careful of new'ing something without a handle; it's an instant memory leak in C++.

Link to comment
Share on other sites

Stephen, I think I found a small bug in the existing core.

 

In tia.cxx:

case HMOVE:
...
Int16 cycle_fix = 17 - ((hpos + VBLANK + 7) / 4); // <-- Shouldn't this be HBLANK (68)???

 

Yes! I wonder what effect it has on ROMS?? This is why we need more than one set of eyes looking at the current code.

 

EDIT: This actually fixes the spacing issues/constant collisions in Kool-Aid Man :)

  • Like 5
Link to comment
Share on other sites

Yes! I wonder what effect it has on ROMS?? This is why we need more than one set of eyes looking at the current code.

Some test code would sure help too. Like preparing a scanline, applying some pokes and then check the results.

 

EDIT: This actually fixes the spacing issues/constant collisions in Kool-Aid Man :)

No surprise. :)

Link to comment
Share on other sites

So I took a break from DK programming and started implementing a simulation of TIA components. This is more a register-transfer-level than a gate-level simulation, more precisely it's at the level of the schematics.

 

Currently, it includes the 'Player Graphics Scan Converter', the 'Player Size Control', and a little bit of the 'Player Position Counter', namely the frequency divider that derives the P-phi1 and P-phi2 clocks from the motion clock MOTCK. (All on sheet #3 of the TIA schematics).

 

Turns out this is quite fascinating. For the counter you make an emulation of a flip-flop, then (virtually) wire some of them together according to the schematics, apply a clock, and ... voilá it counts. :-D

 

The attached file contains all the classes and a main() routine for testing. You can compile and run it with:

 

g++ PlayerGraphicsScanCounter.cxx
./a.out

 

The output then looks like:

 

post-27536-0-56459600-1362095237_thumb.png

 

In this example you can see how the counter is triggered by a strobe of the START signal at (pixel) cycle 3. Since the counter contains 0 it does already address bit 0 of the player, so the enable signal immediately enables output (low-active). NZ are the inverted lower 3 bits of NUSIZ, so we have a quad-size player here and it takes 4 cycles before the counter goes to 1, then to 2.

The interesting part happens at cycle 16, where NUSIZ is changed to single-size while output of bit 3 has already started. There's some delay and it takes 2 pixels before the counter increases to 4. After that all bits are shown as single pixels and the counter rolls over and output is disabled.

 

Interestingly, it looks like the START signal seems to override the STOP signal. Thus, it might be doable to restart the counter while it is still counting. This may make it possible to stretch a quad-size player over the whole screen if RES is hit at the right time.

 

Next, I'll finish the position counter and then plug everything into the TIA emulation and see what happens.

PlayerGraphicsScanCounter.zip

  • Like 4
Link to comment
Share on other sites

Yes! I wonder what effect it has on ROMS?? This is why we need more than one set of eyes looking at the current code.

 

EDIT: This actually fixes the spacing issues/constant collisions in Kool-Aid Man :)

 

This also fixes the barber ROM mentioned earlier in this thread. So one more to scratch off the list.

  • Like 2
Link to comment
Share on other sites

I'm mostly done with implementing a simulation for the Player Position Counter. Basically, everything is now there to draw players. But I decided to debug it first before attempting to couple this with the emulation.

 

It's not working correctly yet, but it can already draw something :). Here we see a line of a quad-size player where the position counter triggers the scan counter. When I try to draw multiple copies it somehow still gets messed up. Maybe I can fix it tomorrow.

 

post-27536-0-93295800-1362275087_thumb.png

  • Like 1
Link to comment
Share on other sites

OK, let me know if there's anything I can do. I'm planning on doing a quick 3.8.1 release to fix a few bugs that popped up, and then I'm moving on to the "multi-bank disassembly to a file" stuff. I'll revisit this as necessary.

 

Please make sure to let me know if I can help in any way. I don't want anyone to feel like I did for years, basically working in isolation without any feedback whatsoever.

Link to comment
Share on other sites

Sorry for disturbing, I'm not a programmer. So, what can I do?

 

NUSIZ? NUSIZ? :?

Search for available free software's knowledge :idea:

 

https://github.com/p....java#L900-L931 I guess that's the right file...

 

Now search for NUSIZ (whatever it is!) the power of Ctrl+F:

Lines 930, 931:

 

 @Override
public void writeByte(int address, byte b) {
final int i = b & 0xff;
final int reg = address & WRITE_ADDRESS_MASK;

if (reg == 0x1B) { /*GRP0 = i;*/ playerDelaySpriteChange(0, i); return; }
if (reg == 0x1C) { /*GRP1 = i;*/ playerDelaySpriteChange(1, i); return; }
if (reg == 0x02) { /*WSYNC = i;*/ bus.cpu.RDY = false; if (debug) debugPixel(DEBUG_WSYNC_COLOR); return; } // Halts the CPU until the next HBLANK
if (reg == 0x2A) { /*HMOVE = i;*/ hitHMOVE(); return; }
if (reg == 0x0D) { if (PF0 != i || playfieldDelayedChangePart == 0) playfieldDelaySpriteChange(0, i); return; }
if (reg == 0x0E) { if (PF1 != i || playfieldDelayedChangePart == 1) playfieldDelaySpriteChange(1, i); return; }
if (reg == 0x0F) { if (PF2 != i || playfieldDelayedChangePart == 2) playfieldDelaySpriteChange(2, i); return; }
if (reg == 0x06) { /*COLUP0 = i;*/ observableChange(); if (!debug) player0Color = missile0Color = palette[i]; return; }
if (reg == 0x07) { /*COLUP1 = i;*/ observableChange(); if (!debug) player1Color = missile1Color = palette[i]; return; }
if (reg == 0x08) { /*COLUPF = i;*/ observableChange(); if (!debug) playfieldColor = ballColor = palette[i]; return; }
if (reg == 0x09) { /*COLUBK = i;*/ observableChange(); if (!debug) playfieldBackground = palette[i]; return; }
if (reg == 0x1D) { /*ENAM0 = i;*/ observableChange(); missile0Enabled = (i & 0x02) != 0; return; }
if (reg == 0x1E) { /*ENAM1 = i;*/ observableChange(); missile1Enabled = (i & 0x02) != 0; return; }
if (reg == 0x14) { /*RESBL = i;*/ hitRESBL(); return; }
if (reg == 0x10) { /*RESP0 = i;*/ hitRESP0(); return; }
if (reg == 0x11) { /*RESP1 = i;*/ hitRESP1(); return; }
if (reg == 0x12) { /*RESM0 = i;*/ hitRESM0(); return; }
if (reg == 0x13) { /*RESM1 = i;*/ hitRESM1(); return; }
if (reg == 0x20) { HMP0 = (b >> 4); return; }
if (reg == 0x21) { HMP1 = (b >> 4); return; }
if (reg == 0x22) { HMM0 = (b >> 4); return; }
if (reg == 0x23) { HMM1 = (b >> 4); return; }
if (reg == 0x24) { HMBL = (b >> 4); return; }
if (reg == 0x2B) { /*HMCLR = i;*/ HMP0 = HMP1 = HMM0 = HMM1 = HMBL = 0; return; }
if (reg == 0x1F) { /*ENABL = i;*/ ballSetGraphic(i); return; }
if (reg == 0x04) { /*NUSIZ0 = i;*/ player0SetShape(i); return; }
if (reg == 0x05) { /*NUSIZ1 = i;*/ player1SetShape(i); return; }

 

And lines: 1346,1347

// Write registers -------------------------------------------

// private int VSYNC; // ......1. vertical sync set-clear
// private int VBLANK; // 11....1. vertical blank set-clear
// private int WSYNC; // wait for leading edge of horizontal blank
// private int RSYNC; // reset horizontal sync counter
// private int NUSIZ0; // ..111111 number-size player-missile 0
// private int NUSIZ1; // ..111111 number-size player-missile 1
// private int COLUP0; // 1111111. color-lum player 0 and missile 0
// private int COLUP1; // 1111111. color-lum player 1 and missile 1
// private int COLUPF; // 1111111. color-lum playfield and ball
// private int COLUBK; // 1111111. color-lum background
// private int CTRLPF; // ..11.111 control playfield ball size & collisions
// private int REFP0; // ....1... reflect player 0
// private int REFP1; // ....1... reflect player 1
private int PF0; // 1111.... playfield register byte 0
private int PF1; // 11111111 playfield register byte 1
private int PF2; // 11111111 playfield register byte 2
// private int RESP0; // reset player 0
// private int RESP1; // reset player 1
// private int RESM0; // reset missile 0
// private int RESM1; // reset missile 1
// private int RESBL; // reset ball
private int AUDC0; // ....1111 audio control 0
private int AUDC1; // ....1111 audio control 1
private int AUDF0; // ...11111 audio frequency 0
private int AUDF1; // ...11111 audio frequency 1
private int AUDV0; // ....1111 audio volume 0
private int AUDV1; // ....1111 audio volume 1
// private int GRP0; // 11111111 graphics player 0
// private int GRP1; // 11111111 graphics player 1
// private int ENAM0; // ......1. graphics (enable) missile 0
// private int ENAM1; // ......1. graphics (enable) missile 1
// private int ENABL; // ......1. graphics (enable) ball
private int HMP0; // 1111.... horizontal motion player 0
private int HMP1; // 1111.... horizontal motion player 1
private int HMM0; // 1111.... horizontal motion missile 0
private int HMM1; // 1111.... horizontal motion missile 1
private int HMBL; // 1111.... horizontal motion ball
// private int VDELP0; // .......1 vertical delay player 0
// private int VDELP1; // .......1 vertical delay player 1
// private int VDELBL; // .......1 vertical delay ball
// private int RESMP0; // ......1. reset missile 0 to player 0
// private int RESMP1; // ......1. reset missile 1 to player 1
// private int HMOVE; // apply horizontal motion
// private int HMCLR; // clear horizontal motion registers
// private int CXCLR; // clear collision latches

 

Again, sorry for disturbing.

But I think it's worth to check all ideas available. That's a great power of free software.

Link to comment
Share on other sites

Just wondering how everything is going. In particular to Thomas and Joe, do you need any feedback from me? Look at some code and/or help out??

Most easy stuff has been done, now comes the complicated part. And currently I am working on Star Castle again, like I had announced before.

 

So finishing the refactoring will take some extra days.

Link to comment
Share on other sites

I'm really pleased and excited that this is happening. Seeing bugs like the Kool-Aid Man issue get swatted without the need for a full rewrite of the relevant code (i.e. pain and mayhem) is pretty great. :) I realize the ultimate goal is probably gate-level emulation, but for those of us whose computers don't have the horsepower to pull off the VCS equivalent of bsnes/higan, these near-term fixes are most appreciated.

 

Question: are audio/sound issues germane to this thread? I know there are more important priorities but I have to admit I'm still curious why the attached "phaser" demo I made (using Andrew D.'s tutorial code) sounds perfect on Stella, but doesn't come off at all on real hardware. Since the effect seems to depend on fine timing, perhaps Stella is making unwarranted assumptions about the startup state of the TIA, and real hardware behaves far more unpredictably. But I understand so little of this stuff that I don't want to speculate.

phaser06.bin

phaser06.asm.txt

Link to comment
Share on other sites

Question: are audio/sound issues germane to this thread? I know there are more important priorities but I have to admit I'm still curious why the attached "phaser" demo I made (using Andrew D.'s tutorial code) sounds perfect on Stella, but doesn't come off at all on real hardware. Since the effect seems to depend on fine timing, perhaps Stella is making unwarranted assumptions about the startup state of the TIA, and real hardware behaves far more unpredictably. But I understand so little of this stuff that I don't want to speculate.

 

It's good to have this posted here for the sake of completeness, but I don't know when (or who) we'll get to this. I think our main concentration is TIA graphics stuff. This is probably more of a sound code thing, which is (a) a separate part of the codebase, and (b) not really researched. Most (all?) other emulators use Ron Fries original TIA sound code, so I suspect the problems with this ROM are there.

 

Anyway, something we should definitely get to, but probably not for a while. But who am I to say for sure. The RSYNC and HMOVE fixes came within days of me asking for help :)

  • Like 1
Link to comment
Share on other sites

I realize the ultimate goal is probably gate-level emulation, but for those of us whose computers don't have the horsepower to pull off the VCS equivalent of bsnes/higan, these near-term fixes are most appreciated.

I don't think we will have to go to gate-level emulation. You can abstract a lot of things without loosing correctness. The most important things is to emulate the various clocks correctly. This will be not very easy, but also no rocket science.

Link to comment
Share on other sites

If there is the idea of implementing the various Player/Missile/Ball position and scan counters, I would personally (and humbly) suggest you guys take a look at Javatari's TIA emulation code.

It does exactly this and uses no tables. Every TIA clock is processed to trigger/start/stop each counter and determine if the object is in the middle of a scan, and whether to have its pixel ON or OFF.

It makes some assumptions for the sake of performance and is not "gate level accurate", but I think its already working pretty decent. Some of the games with problems that are listed in this thread work OK in Javatari.

 

https://github.com/ppeccin/javatari/blob/master/javatari/src/org/javatari/atari/tia/TIA.java

 

I'm not much into C/C++ coding, but I offer myself to give some ideas and help you guys understand my approach at this if you are interested.

 

Regards,

Peccin

Link to comment
Share on other sites

Question: are audio/sound issues germane to this thread? I know there are more important priorities but I have to admit I'm still curious why the attached "phaser" demo I made (using Andrew D.'s tutorial code) sounds perfect on Stella, but doesn't come off at all on real hardware. Since the effect seems to depend on fine timing, perhaps Stella is making unwarranted assumptions about the startup state of the TIA, and real hardware behaves far more unpredictably. But I understand so little of this stuff that I don't want to speculate.

I think the biggest issues you're running into with the audio are that changes to the frequency registers can take a while to propagate in some cases, and changes to the control registers might also take a few bits before the new pattern starts to get output-- the frequency being the larger issue.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...