1:04 Verfify, cousin to Covfefe. Late to the party but following along in VS as a SQL Developer with some C++ experience and thank you for making this playlist!
@Wishbone1977 Жыл бұрын
Yup, that's a pet peeve of mine: making a typo/spelling error, not paying attention, and then copy-pasting that error all through the code base, making it that much harder to correct. Bonus points if it's a database column name.
@mastercko Жыл бұрын
I wonder if he will ever catch that...hehe
@cigmorfil41013 жыл бұрын
15:35 The page boundary cross is on the computed address: absolute + index_register. As the 6502 uses synchronous memory access after it has added the index to the LSB it reads from the memory address so created, but if there was a carry it requires incrementing the MSB to the next page, and a second read (256 bytes later in memory) to read the actual data - this is what takes the extra clock cycle. On writing the short cut of writing before checking the carry cannot be done as it risks splatting memory, so the indexed writes always take the extra cycle, even if not required.
@BryanChance3 жыл бұрын
This is such an awesome project! A deep understanding of CPU operations.
@lordg13m2 жыл бұрын
Hi Dave, at 41:03 for the 'if condition' on crossing over a page boundary for INS_LDA_ABSX, wouldn't "if ((AbsAddressX & 0xFF00) != (AbsAddress & 0xFF00))" work better? Maybe you fix this later but it seems to me the difference of the two could be less than 0xFF and it might still cross the page boundary.
@sgermain06 Жыл бұрын
That's exactly what I did too. I did mine a bit differently: "if ((AbsAddressX >> 8) != (AbsAddress >> 8))"
@ng347804 жыл бұрын
The reason you couldn't get the test to enter the "infinite loop" is because you changed the Cycles variable from unsigned to signed, which would make the while loop stop execution because Cycles went below 1, before Cycles would decrement from eg. 0x02 to 0xffff and would be bigger than 0x0 in the while loop.
@maxine_q3 жыл бұрын
Also technically the loop wasn't infinite (although it could be in some cases). It would just take a while for the cycles counter to come down to 0 again after it wrapped around and printing "instruction not handled" about 4294967296 times.
@patrickelliott21693 жыл бұрын
Am am honestly semi-head scratching over the use of cycles anyway. In practical terms the CPU of a real computer isn't going to "know" how many cycles to expect, so this is going to change, later, when it involves actually running code? Alternatively, I could see it as a way to force a wait state, between cycles, so you can approximate (but probably not exactly match), to the timing of the original CPU being emulated. But.. I suppose that there will be a lookup table added at some point, so when the "CPU" is stepping through things, it will look at the next opcode, look up number of cycles needed, then fire the execute command? I mean, I guess that is the long term plan, or something like it. But, definitely not at all the approach I would have thought of. lol
@cigmorfil41013 жыл бұрын
@@patrickelliott2169 My thought would be to count *up* the cycles as a single instruction is decoded and executed, and then have a main loop that executes a single instruction, takes the cycles returned by the instruction and adds it to a cycles clock (count). This total count would then be checked against the total cycles expected since some fixed point in the past (based on how long this is and the clock speed emulated). If too many cycles had been executed it would wait until the required time (for the cycles it had reached) . If too few it would just continue - as the emulated execution is expected to be faster than a real CPU any lag can be caught up over the next few instructions. This check need not be every instruction, but at some rate so that the cpu would appear to be running smoothly (though for timing NOPs in a loop a quicker rate of check would be better). Every so often the zero point for time would be reset as would the total cycle count. Lag could be caused by the program having to emulate an environment, eg a PET emulator would need to emulate the screen (and the 6522s) which will take time (or just getting swapped out by the host kernel). With a single instruction loop you could build a "debugger" into the CPU emulator which could be started by a specific instruction (eg $?2 except $A2 which all cause a real 6502 to get stuck until reset, or any non-documented/standard OP Code) allowing you to examine (and change) the state of the machine (CPU + emulated environment), or trap a signal to the program to set a debugging flag which then gets checked after the current instruction finishes (the same loop could be used to allow the debugger to single step an instruction at a time). Different signals could be trapped to emulate IRQ, NMI and Reset in a similar way: set a flag which is checked after the current instruction finishes and then implement the required interrupt.
@75slaine3 жыл бұрын
@@cigmorfil4101 I was thinking along these lines too while following along.
@ast_rsk2 жыл бұрын
Half of this content is a great example of navigating documentation and logic. The other half is how to be a good SDET. Well done!
@MichaelDSwanson2 жыл бұрын
Excellent series! This particular video is a great illustration of why you code one test first, get it to run, then move on to the next test.
@maxine_q3 жыл бұрын
In your zero cycles test you could also check that the internal cpu state did not change. There is a way to test for an exception to be thrown, don't know what it's called exactly now. It's something like expect_exception.
@sandervanderhorst98513 жыл бұрын
You can actually do EXPECT_THROW for the invalid instruction test (39:17)
@m1geo Жыл бұрын
Came to say this! 😂
@TrossachsPhoto3 жыл бұрын
I might not be caught up (I'm at 41:07). But the crossing page boundary check would be (AbsAddress % 256) + X > 0xFE. If AbsAddress is 0x0000,0x0100,0x0200... and X is is FF, then the first byte read will be in page but the second byte will be crossing a page break. Hence the 0xFE previously, rather than 0xFF.
@DavePoo3 жыл бұрын
Yeah well spotted, that was some dodgy code i wrote there. It got fixed in a later video. I went with this "const bool CrossedPageBoundary = (AbsAddress ^ AbsAddressX) >> 8;"
@alienrenders3 жыл бұрын
Wonder if it wouldn't be simpler to just have a table with the number of cycles and use that and adjust it for special cases. Seems tedious to try and figure what sub steps use up what cycles.
@AlphaPidiPadova4 жыл бұрын
@Dave, First of all, thank you for your intersting videos :) I have a doubt regarding the additional cycle when the page boundary is crossed. As page 0 is from 0x00 to 0xff and page is from 0x100 to 0x1ff, if I point address 0xf0 and I add 0x20, I go to 0x110. That means that I crossed from page 0 to page 1 even if the difference between addresses is 0x20, that is less than 0xff Maybe your check ( AbsAddressY - AbsAddress) >= 0xFF could be wrong? Thank you again, cheers from Italy a
@DavePoo4 жыл бұрын
Thankyou. Yes, i think you are right about the page boundary code. It was also pointed out by a viewer and it doesn't get fixed until the last video in this series.
@Xenthera4 ай бұрын
For anyone reading this, the solution is simple, just compare the old page and the new page. if(absoluteAddress >> 8 != absoluteAddressX >> 8){ // crossed page cycles--; }
@tiqo85493 жыл бұрын
Listen..i had some drinks..4:30am in the morning here (Netherlands), and i watched this pretending i understood what this was all about. Well...i don't..but it's still more fun to watch than all this covid shit. Rock on mate ;-)
@tmbarral6644 жыл бұрын
@Dave: Quick question: why the curly braces in the case ? is it necessary now with C++ ? local context ? And why not place the break inside the block too ?
@DavePoo4 жыл бұрын
Why the curly braces? Well because C/C++ treats the entire switch statement as a single scope (as the switch has a compound statement scope {} after it). So if you try and declare local variables inside the case statements, you will either get duplicate variable declaration errors or you will get errors that other variables have not been initalised (as when the program jumps to the case it skips over the initialisation of variablies in the above cases) , basically you can try this yourself by taking the {} out of the cases and the program won't compile. As for putting in the break inside or outside of the {}, i just put it outside by convention as I think it looks neater, but I think it would work just the same inside the scope as long as you put it at the end.
@PeterCCamilleri3 жыл бұрын
OK, I can remain silent no longer. The implementation of the chip reset is still wrong. Address $FFFC and $FFFD contain the address where execution begins. Execution does NOT begin at $FFFC as you have done unless those two location contain $FC $FF. This also invalidates all of your tests.
@DavePoo3 жыл бұрын
Yeah. I never did handle that reset properly.
@sirgouki62073 жыл бұрын
No, it doesn't invalidate ALL of the tests. If the expected result is for example to load A with 11, the reset should have no effect on the ability of the CPU to set A to 11. Sure, it *may* invalidate other things, but it doesn't invalidate everything just because *you* say it does.
@PeterCCamilleri3 жыл бұрын
@@sirgouki6207 Well it depends on your point of view. If the reset action were fixed, none of the _tests_ would run properly. They would all break because the execution point would be at some crazy address. To cite your example, the lda code would still be valid, but the test code for lda would need to be rewritten to work with the corrected reset action.
@alienrenders3 жыл бұрын
@@PeterCCamilleri If he fixed the vector, all he needs to do is set the PC to 0xFFFC in the SetUp method. Literally one line fix.
@PeterCCamilleri3 жыл бұрын
@@alienrenders Could be, but the fix still needs to be applied to all the tests.
@arkanjo75093 жыл бұрын
thanks
@Salsuero4 жыл бұрын
How do you test Absolute Y when you've not changed the cpu.X value to cpu.Y after copy/paste/edit? Yep. A mistake. Debugged.
@64jcl3 жыл бұрын
I assume you adjust it later but its a bit odd that you pass in the number of cycles ahead in your execute method when the number of cycles is derived from the opcode.
@DavePoo3 жыл бұрын
In Execute, I pass in the number of cycles I would like to perform before exiting that function.
@64jcl3 жыл бұрын
@@DavePoo yes I saw that but running the emulator on a piece of code you dont really know how many cycles you want to execute, normally if you are stepping through code its one instruction at a time where some instructions actual cycle count will vary. Will your code be able to execute partially into an instruction and then continue that execution on the next time its called? From what I see you can end up with negative cycle count when it exits which sort of indicates the trouble of knowing ahead of time how much you want to execute. Perhaps you have a purpose for this that I dont understand and I have not come far into your series. :) - Btw I am a developer and do C64 games in my spare time and have also been working a bit with my own C-like language compiler into 6502.
@DavePoo3 жыл бұрын
@@64jcl The Execute always finishes it's instruction. In theory you could always just pass in 1 and it will single step instructions
@MrPDawes3 жыл бұрын
I don't know C++ but I absolutely agonized over the confusion over words and bytes and indirect operators. Welcome my my world of assembler. So easy.
@daemonmagic8261 Жыл бұрын
somebody should to tests for your tests :-)
@ahmadhadwan3 жыл бұрын
im just wondering why do you simplify code by putting it into a new function, why dont you use a macro?
@DavePoo3 жыл бұрын
I'd only use a macro if i am trying to get the compiler to write code for me that can't be done via a method, or is something really simple. Macros are a necessary evil for C/C++, so i would use them but they would never be my first choice. The compiler can inline functions anyway so small functions can be optimised away by the compiler, there is no need to try and use a macro to get that functionality. Also, putting code into a function (or macro) doesn't make it simpler, it makes it more complicated to read. You generally want to put code into a function when you intend to call it in more than one place. I think macro's can be worse than functions as they abandon all the type checking.
@cigmorfil41013 жыл бұрын
@@DavePoo They can also have side effects as they are effectively a textual substitution: #define max(a,b) ((a)>(b) ? (a) : (b)) seems harmless enough until you have: int *ptrA, *ptrB, c; ... c = max(*++ptrA, *++ptrB); which "expands" to: c = ((*++ptrA)>(*++ptrB) ? ( *++ptrA) : (*++ptrB)); and it doesn't quite do what you were expecting and is hard to spot the bug. Similarly for c=max(ptrA[++i],ptrB[j++]); becoming c=((ptrA[++i])>(ptrB[j++]) ? (ptrA[++i]) : (ptr[j++]));
@geektech11132 жыл бұрын
For LDA_ABS my fetch word function value is changing after being returned Fetch Word Data : 17536 Variable Data from FetchWord function : 128 Accumalator : 0 here my fetch byte function value is 0x4480 but after storing it in variable it becomes 0x0080 which results in no change in my a register
@geektech11132 жыл бұрын
Can someone help me with this
@rohithbhandari78368 ай бұрын
I dont really understand why there is CPUcopy ?
@thezood3 жыл бұрын
N00b question: what's the difference between FetchByte and ReadByte?
@DavePoo3 жыл бұрын
FetchByte() is reading from where the program counter is right now and then incrementing it. ReadByte() is reading from a given memory address.
@thezood3 жыл бұрын
@@DavePoo Cool, thanks
@GuyPaddock Жыл бұрын
Naming should really be like `FetchProgramByte()` and `ReadMemoryByte()` for clarity.
@HandsomeLukeMan3 жыл бұрын
I wanna know what keyboard he uses
@DavePoo3 жыл бұрын
www.cherry-world.com/cherry-mx-board-1-0.html
@HandsomeLukeMan3 жыл бұрын
@@DavePoo What switches?
@DavePoo3 жыл бұрын
Actually lied, it's an mx board 3.0 What switches? the ones it came with.
@Voando44S4 жыл бұрын
Parceiro encina a decopila libil2cpp
@oldlonecoder58438 ай бұрын
I am late. Jumping on the train and landed here . Why playing with cycles as a counter?? Why waste efficiency on useless meaning because the cycles are already calculated for each opcode and it is there to be used not as a decrement counter but in fact it is a value to be used in the call to "usleep(addrmod::cycles)" !! Having the real time delay between opcode executions in today's gigahertz CPU. The 6502/10 is exactly 1 mhz! And usleep(unsigned long cycles ) is also exactly cycles x 1/1 000 000 ! So we Can simply apply the above simple call to usleep...