Emulating a CPU in C++ (6502)

  Рет қаралды 403,975

Dave Poo

Dave Poo

Күн бұрын

Пікірлер: 874
Aaronjamt
Aaronjamt 2 жыл бұрын
BTW, the SP (stack pointer) should only be a Byte (8bits) not a Word (16bits)
Curt J. Sampson
Curt J. Sampson Жыл бұрын
@Christoph Franzen The stack does not grow downards in order to detect the SP reaching zero; most 8-bit CPUs do not detect this, nor do even many early 16-bit CPUs. Systems that do offer memory management almost invariably have registers or data structures indicating the range within which the stack must remain. The stack conventionally grows downwards because code and other data grow upwards; this makes the most efficient use of memory by allowing the two to grow towards each other until they meet, rather than having to assign an arbitrary size to each. Microsoft BASIC of the era does the same with its second software stack, which starts directly underneath the string heap and grows down, while BASIC program text grows upwards from the bottom of the memory allocated to BASIC, with its variable storage allocation growing upwards from just above the program text.
Curt J. Sampson
Curt J. Sampson Жыл бұрын
@MrByteYT I am fairly certain that the stack pointer is not initialized on the Z80; it's certainly not initialized on the 8080, 6800 or 6502. There's no point in adding circuitry to do the initialization since the reset code can just do that in software. (Or not re-initalize it if it's already set to an appropriate value.) The initial value after the CPU has been powered off for a period will be somewhat random; the initial value after a reset during a power-on state will be the same as the value before the reset.
Steve Porter
Steve Porter Жыл бұрын
Saw that off the top myself glad to see the comment here :)
Christoph Franzen
Christoph Franzen Жыл бұрын
@MrByteYT But this defeats the reason for the stack growing downwards, which is: because it is cheaper in hardware to detect SP reaching zero, which is a stack underflow happening if you pop more than you push
Curt J. Sampson
Curt J. Sampson Жыл бұрын
@Sean Pearce ​ @Sean Pearce The stack doesn't actually need any overflow handling if you implement it as an 8-bit register, always adding 0x100 to it to produce the memory address when you read/write the stack. Decrementing an unsigned byte 0x00 in C should set it to 0xFF, though I'm not sure what the standards say about this. For another take on how to write an 8-bit CPU simulator, you might have a look at the 6800 simulator I wrote in Python. The code for this one is focused on describing the instruction behaviour as clearly as possible, so I hope it should be readable without too much difficulty. (Note that the 6502 stuff in adjacent directories is just a wrapper around py65; the 6800 and 6502 both present a common interface to my unit test system for assembly code.) github.com/0cjs/8bitdev/tree/ab43753ca796f7bff3648247ce9e137ede1229c9/lib/testmc/mc6800
Philippe LePilote
Philippe LePilote 2 жыл бұрын
How astonishing to find this YT suggestion ! I wrote a 6502/6503 emulator in 1987 in C on a PC-XT (8086). Both clocks of 6502 and 8086 were at 4Mhz. The emulation was 400 times slower than the real processor, but it was embedded in a debugger (MS C4-like) and it was possible to set breakpoints, survey memory values, execute step by step, a.s.o... Ahh ! nostalgia...
Dons pad
Dons pad 5 ай бұрын
@alb12345672 yes, lol....
Dons pad
Dons pad 5 ай бұрын
@Miguel D goose! Lol....
Dons pad
Dons pad 5 ай бұрын
AND!!! The 8088 is a REAL PIPELINED PROCESSOR, WITH A REAL REGISTER BANK, etc..... a REAL PROCESSOR, not a CUT DOWN TOY like the 6502...
Rubix Cube
Rubix Cube Жыл бұрын
@Philippe LePilote You gave me nostalgia although I wasn't even born yet! I like these stuff and I picture myself doing this if I was in this era... I'm here to remind you to make videos about it I will enjoy this so much!
Philippe LePilote
Philippe LePilote Жыл бұрын
@Aaron Gibson Why not one day... Something to consider...
Leandro Calil
Leandro Calil 8 ай бұрын
Great content and great video! 👏👏👏 Here are a few suggestions and things I noticed and would like to point out: 💡 Instead of relying on the width of primitive types for the host platform, using with its uint8_t, uint16_t and so on will make your code more elegant and platform agnostic; 💡 The stack pointer (SP) should actually be 8 bits wide. The 6502 will add 0x100 to it, thus making it able to range from 0x100 to 0x1FF; 💡 Upon reset, the 6502 will not set PC to 0xFFFC and execute from there. Actually, it will set PC to what memory location 0xFFFC points to (least significant byte first); 💡 For your FetchWord() implementation, you don't really have to worry about the endianness of the machine you're compiling your program for. That because endianness affects how numbers are laid out in memory only, and the 6502 will be little endian regardless. Numbers _per se_ and how you handle them will be the same regardless, thus (v
Dave Poo
Dave Poo 8 ай бұрын
Thanks. The stack pointer was fixed in a later episode -> kzbin.info/www/bejne/n2ath3Z-iLOrgLs . I don't think I ever got the reset correct, but it wouldn't really affect this implementation as I'm not actually getting a working computer (just a CPU). You are correct about the Endian thing, it was fixed here -> kzbin.info/www/bejne/n2ath3Z-iLOrgLs
Tony Bright
Tony Bright 2 жыл бұрын
Did a FULL implementation back in 1986 in C to simulate machine tools controlled by a 6502. It was cheaper to test code on a PC before putting it into a tool than it was to put code on the tools and have it break something. Probably would have been easier in C++ as you could model the various components of the CPU as objects.
Tony Bright
Tony Bright Жыл бұрын
@sempertard better part of a couple of months. I was working part time and still working on my CS and EE degrees. So maybe 20 hours a week working while going to school.
sempertard
sempertard Жыл бұрын
Those were the days... Throwing code that actually DOES something is so much more rewarding than crunching rows in a DB and spitting out a PDF report. Tony; just curious, long did it take you to do the emulation code?
TheGaming Hobo
TheGaming Hobo 2 жыл бұрын
That’s awesome godamn!!
Lee W
Lee W 2 жыл бұрын
Great video Dave, thanks for sharing. I did a similar thing nearly 30 years ago. Wrote a Z80 emulator in compiled Blitz Basic on the Commodore Amiga. Got it to load and 'run' ZX Spectrum snapshot game files. It could actually render the Speccy's screen but my god it was slow - took about 5 minutes to render one frame! A 0% useful but 100% interesting project!!
ernestuz
ernestuz 2 жыл бұрын
If you use uint8_t and uint16_t for your CPU types (in ) you make your code basically platform agnostic, now you depend on 16 bit shorts.
Bob Weiram
Bob Weiram Жыл бұрын
You're all discussing petty optimization issues solved by the Ada programming language back in the 70s. It requires all declarations to be explicitly typed. Numeric types must have a range (lower and upper bound). Portability is a non-issue as the compiler determines the optimum integer size and the platform endian needed. The resultant code is small and performant enough to run on resource limited embedded systems. It can also be transpiled to C++, allowing it to leverage the best possible optimization.
ggzh a Argue With Everyone
ggzh a Argue With Everyone Жыл бұрын
@Mensa Swede int is also useful for many other things, like a file descriptor or pid. You know smaller machines where int is 16 bit will probably never need more than 32767 PIDs, so a16 bit PID is fine but larger machines may need more, but there a int is larger anyway.
ggzh a Argue With Everyone
ggzh a Argue With Everyone Жыл бұрын
@Mensa Swede To make compiler simple and programs small. Back then many machines didn't had 8 bit bytes, requiring 8, 16 or 32 bit integers would have make c non-portable. int is useful for return values where it indicates success or not, like -1 is for error, 0 for didn't do anything and 1 for success. It is also useful to hold a single character, it can have the unsigned char value of a character or -1 for not assigned/error, like it does for getc(). . Most used primitive data type which is not a pointer in application is size_t anyway.
Mensa Swede
Mensa Swede Жыл бұрын
@ggzh a Argue With Everyone I’m not saying all machines need a 32 bit int. I’m saying that to design a language where the primary way to declare an integer tells the program author nothing about what values it can hold, was not the best decision. Look they could have left “int” (with its nondescript size) in there in the original design, but they should have included int8, int16, etc. If I am writing a C program from scratch today, there is far more use to use int8/int16 than “int”. What possible benefit was there to leaving out int8/int16 in the original design?
ggzh a Argue With Everyone
ggzh a Argue With Everyone Жыл бұрын
@Mensa Swede No, it is not a mistake. There are many different machines. It makes no sense to have a 32 bit int on a 8 bit machine (which are still common). . Don't use in when you need to be able to store the value 75000. Use uint32_least_t or uint32_fast_t.
Steve Jones
Steve Jones Жыл бұрын
I have a problem with the way the emulator appears to be trying to merges the emulation of multiple elements of a computer as one lump rather than as discrete components tied together by the master clock. To me, the bit of the code that is emulating the microprocessor ought to do only that. That means things like the registers of course, but it should not include the memory. That's strictly external, and what should be emulated is the state of the physical interfaces that allow memory to be read from and written to. What's just as critical, is that includes any I/O devices which are all memory mapped on a 6502. I would even expect and emulator to model the state of all the output pins of the 6502 chip at any point. By making the whole thing clock driven it means that there's a chance of it actually behaving like a real computer with proper time synchronisation. It's fine to have an emulator which runs faster than the real thing, but in a controlled way (fine clock control may not be completely possible with sub-microsecond timing but there are ways to deal with even that). All the necessary discrete components of the computer ought to have their own definitions so that it's possible to build up a "virtual computer" that is made up of components. That should include and external clock, complete with phases, which runs as master loop to drive the execution of instructions and the interfaces to any other components, among which is memory. Memory is not part of a CPU definition. Instead, what is required is one or more discrete memory emulators which can be mapped into the address space. Memory isn't necessarily contiguous, and there are both RAM and ROM forms too. Then there are I/O devices, which on a 6502 are memory mapped, and those too ought to have their own discrete definitions. Things like keyboard interfaces, serial drivers, sound chips and so on. They don't, of course, all have to be there from the start but the ability to add them will make the whole model much easier to extend. As a guess, the starting position ought to include that master clock, the microprocessor emulation, RAM & ROM emulators and some initialisation meta-routine that allowed memory to be loaded with a program. I suspect some of these emulations, like RAM and ROM, will be very simple but it seems to me important that the CPU emulator ought to mimic the setting of the address, data and R/W pin and that this is what ought to drive memory reads and writes (as well as any other memory mapped device). This doesn't mean that the whole thing must be produced at once, nor that the some short-cuts can't be used to mimic behaviour, but they ought to sit within the model of discrete components. For example, routine to populate memory with a program to be executed, but that would be outside the actual emulation routines themselves. That way it could be easily removed once a more general routine to load programs was introduced. For example, something that read in from an emulated disk or tape interface. Perhaps easier is an emulation of a cartridge being inserted and appearing as ROM (real 6502 computers often had paging registers to allow memory overlapped ROM cartridges). The issue to me is that this is being thought through from the point of view of software design, and not that of an electronic engineer. If somebody wants an accurate emulation, then it's critically important that it should reflect the structure and interfaces of a real computer. Perhaps not as precisely as simulating periods of rising and falling clock and interface signals, at least for this sort of use, but at least something that models the working of a computer.
Brian Angulo
Brian Angulo 6 ай бұрын
Thank you for taking the time to comment. I found it insightful.
GaryChap
GaryChap 2 жыл бұрын
To anyone thinking about coding their own... Most processors, internally, use predictable bits of the instruction opcode to identify the addressing modes - because, the processor really needs to be able to decode opcodes fast, without having to 'think' about it! Understanding this strategic bit pattern can make writing a CPU emulator SO much easier! It's been a long time since I coded for 6502 ASM ... but, if you were to plot each instruction in a table, you'd likely notice that the addressing modes fall into very neat predictable columns. This means that you can identify the 'instruction' and 'mode' separately, which then lets you decouple the Instruction logic from it's Addressing logic. This 'decoupling of concerns' can really help shorten your code and reduce errors _(less code, as every Instruction-Type is "addressing agnostic" ... and less repetition, as each "Addressing logic" is only written once and is shared across all instructions)_ Just an idea for future exploration : ) Unfortunately, sometimes this bit-masking strategy isn't perfect, so you might have to handle some exceptions to the rule. *My experiences, for what it's worth...* Last time I emulated an 8-bit fixed-instruction-length processor... I wrote each instruction handler as a function, then mapped them into a function-pointer array of 256 entries. That way (due to ignoring mode differences) several opcodes in an instruction group all called the same basic handler function. I then did the same thing with the modes, in a separate array ... also of 256 entries. So, every Instruction was invariably a call to : fn_Opcode[memory[PC]] ... using the mode handler : fn_Mode[memory[PC]] That got rid of any conditionals or longwinded case statements... just one neat line of code, that always called the appropriate Opcode/Mode combination... because the two tables encoded all the combinations. Hope that makes sense ; ) Obviously, to ensure that this lookup always worked - I first initialised all entries of those tables to point at the 'Bad_Opcode' or 'Bad_Mode' handler, rather than starting life as NULLPTRs. This was useful for debugging ... and for spotting "undocumented" opcodes ; ) It also meant I knew I could ALWAYS call the function pointers ... I didn't have to check they were valid first ; ) It also meant that unimplemented opcodes were self-identifying and didn't crash the emu ; ) As I coded each new Instruction or Mode, I'd just fill out the appropriate entries in the lookup arrays. But the real beauty of this approach was brevity! If my Operation logic was wrong, I only had to change it in one place... and if my Addressing Mode code was wrong, I only had to change it in one place. A lot less typing and debugging... and a lot less chance for errors to creep in. Not a criticism though... far from it! I just thought I'd present just one more approach - from the millions of perfectly valid ways to code a virtual CPU : ) Understanding how the CPU, internally, separates 'Operation' from 'Addressing' quickly and seamlessly... is damned useful, and can help us emulate the instruction set more efficiently : ) But, ultimately, you might have to also handle various "ugly hacks" the CPU manufacturer used to cram more instructions into the gaps. By using two simple lookup tables, one for Operation and another for Mode ... you can encode all of this OpCode weirdness in a simple efficient way... and avoid writing the mother of all crazy Switch statements XD
GaryChap
GaryChap 2 жыл бұрын
@Dave Poo Oh, that's such a shame : ( It's so sad to think of all the things we lose along the way. Not just the code, the files and the hardware... but the childlike wonder when we got our first 8-bit, or the thrill of making a modem connection and manipulating some machine at a distance. Even just groking peripheral ICs for the first time... poking at some addresses or registers and making things happen (or not) Bah! Growing up sucks : ) Still, I'm so glad I got to do it in the 70s/80s when computers were still a wild frontier and understanding ASM, and the bits on the bus, was the only way to get anything done in a reasonable time. Heroic days :D Now we all walk around with supercomputers in our pockets, and never give it a moments thought : / There's that quote about advanced technology being indistinguishable from magic... ... the unfortunate corollary of it is that the more ubiquitous advanced technology becomes - the less 'magic' there is in the world. Thanks for making your videos... stuff like this is slowly becoming a new field ... digital archaeology : ) Heh, I guess that makes me a relic XD Anyway, thanks for doing what you do.
Dave Poo
Dave Poo 2 жыл бұрын
@GaryChap At least your old drives work (or exist). I went back to my Amiga 600 to see if the first machine code game i ever attempted was on there, but the hard disk was missing, i think i must have sold the drive or the Amiga in the past and completely forgot. It turns out 30 years is a long time.
GaryChap
GaryChap 2 жыл бұрын
Ahhh! I just found my old code! The Generic 8-bit fixed-length CPU frame... using the opcode call-table so that you could load up CPU personalities as plugins. The plan at the time was to emulate all the 8-bit fixed-length families and their variants... but I guess life got in the way. I had a couple of almost identical z80 variants, an i8008 and i8080 and a classic (non-C) 6502 ... and, at some point I'd added a simple 8-instruction BF (brainf**k) machine from esolang. I'd completely forgotten writing most of this : )))) I love exploring old drives, some of it makes me cringe : )
GaryChap
GaryChap 2 жыл бұрын
@Dave Poo Oh, absolutely : ) But isn't that the wonderful thing about this project. There's a lot of ways you can go... all with their own little tradeoffs. I think the important thing is that people have a go - and don't be afraid to stray from the path and see where it leads : )))) I love that there are channels like yours, encouraging people to tackle things like this. 8-bit particularly tickles me, because it's where I got my start in game dev back in the mid-late 80's. Happy times : )
Dave Poo
Dave Poo 2 жыл бұрын
I do agree with you. But I would say that even with my huge switch statement method, i do only write the address mode functions once and then reuse them. Secondly, it's still possible to screw up the lookup table method just as easily as the giant switch statement method (as you still have to fill the tables correctly) , so you would still have to do the unit testing for each instruction that i'm doing to really be sure. I would say the giant switch statement method i have here, is only really good for a small processors like this (150 instructions), it's very easy to read and easy to reason about. If i tried to do this for anything more complex like the Motorola 68000 then i would no way attempt it this way, i would certainly be using the method you are describing above. If you make each opcode into a switch case on the 68000 i'm pretty sure you would have multiple 1000's of switches.
Dexxter
Dexxter 2 жыл бұрын
On a similar theme, back in the 1980's I wrote an assembler/disassembler pair for the Z80 microprocessor that ran on a Pyramid minicomputer. I used it to work out the full functionality of UHF radio scanner that had a Z80 and associated IO chips as it's central control. I dumped the radio's 16k byte EPROM into a file containing a long string of HEX pairs, disassembled it and printed the result. Then spent a few days looking at the printout and filling it with comments. Made my modifications, also adding all the comments to the disassembled program and used my Assembler to create a new HEX file ready for EPROM programming. Started up the radio and all my mods were working as planned. They were fun days. I doubt I could do what I did back then with today's systems.
Ronaldo Prado
Ronaldo Prado 2 жыл бұрын
I have tried to do similar thing for years, not with UHF radio but with another kind of ROM. Anyway I couldn’t to do. My level of knowledge is low and I think I am a bit lazy . . . he he he
Ronaldo Prado
Ronaldo Prado 2 жыл бұрын
It’s amazing. Congratulations for a very interesting job!👏👏👏
davesherman74
davesherman74 2 жыл бұрын
Very interesting. Earlier this year I was wanting to expand my knowledge of Java and went through a similar exercise. I had a Heathkit ET-3400A microcomputer a long time ago, and I wrote a functional ET-3400A emulator that runs the ET-3400A ROM in an emulation of a Motorola 6800.
Louis Huemiller
Louis Huemiller 2 жыл бұрын
At 5 minutes into the video, it is stated "So this is where you have to know exactly the size of a certain type on your platform or compiler". Doing this creates platform-specific code, which only works on platforms with the same type sizes. Instead, it is better to use the platform-independent types that are declared within . Specifically, the followings lines in the author's code: using Byte = unsigned char; using Word = unsigned short; should be something like: typedef unit8_t byte_t; typedef uint16_t word_t; It's debatable whether a using or typedef statement should be used, but the key thing is the use of uint8_t and uint16_t from .
Alastair
Alastair 7 ай бұрын
@Dave Poo im pretty sure you can use Uint16 and Uint32 it works for me
Dave Poo
Dave Poo 2 жыл бұрын
Yep, i could have used those, but i was careful to use my aliases everywhere so it's trivial to fix them later. I prefer Word & Byte to everything having an _t on the end. Not sure why they did that. Would it have been so bad to call them uint8 and uint16 instead? There is no difference in typedef and using in this case other than the syntax, but i prefer "using" to typedef. unsigned char is guaranteed to be 1 byte anyway by the spec.
Nelson Waissman
Nelson Waissman 2 жыл бұрын
Ive coded my 6502 emulator in C, so very similar to yours. You've probably fixed this later, but just mentioning that the stack is an 8 bit register and it starts from FF downwards. The actual memory used is from 1FF to 100 (so the processor adds 100 to the register value). And remember, you will need to store the stack pointer register in the stack itself, as a single 8 bit byte. Looking forward to watching your next videos.
Dave Poo
Dave Poo 2 жыл бұрын
Thanks. You are right about the stack pointer and I got around to fixing it in#8 kzbin.info/www/bejne/n2ath3Z-iLOrgLs
Cigmorfil
Cigmorfil 2 жыл бұрын
08:30 You are missing the unused (expansion) flag of the 6502 in bit 5 between B and V (I'm assuming your compiler assigns the bits from the least significant bit 0 upwards). Without this bit some processor status manipulations (such as PHP, PLA, play with bits, PHA, PLP) could fail as V and N would be stored in the wrong bits.
Dave Poo
Dave Poo 2 жыл бұрын
Yes, but it was added at a later date
TheDarkSide11891
TheDarkSide11891 2 жыл бұрын
Been looking for a series like this for years, bloody brilliant work mate! Keep it up!
Dave Poo
Dave Poo 2 жыл бұрын
Thanks. I said in this video i wasn't going to write the whole thing. But i realised nobody had done the whole thing on video before, and i thought it would be good for people to see how much work it could be to get something like this working.
etmax1
etmax1 Жыл бұрын
You use the term "clock cycle" for what is actually a machine cycle. Early processors such as the 6502 required multiple clock cycles to execute one machine cycle. The 68HC11 for example needed 4 clock cycles for each machine cycle.
Michel GRIGAUT
Michel GRIGAUT 8 ай бұрын
@Peter Müller Except RISC (Reduced Intruction Set Cpu) processors like the 6502 where 1 clock cyle is the only unit
Peter Müller
Peter Müller Жыл бұрын
@My name is of no consequence. To my knowledge, this is not true. The CPU needed multiple clock cycles per instruction for instruction loading, decoding etc
etmax1
etmax1 Жыл бұрын
@My name is of no consequence. a clock cycle is the actual oscillator frequency, or crystal frequency, while a machine cycle is the internal cycle count which depending on the processor is between 1 clock cycle per machine cycle and I think the worst I saw was around 7. Intel Architectures (8080/Z80/8051 etc.) used to have higher counts where as Motorola and others including 6502 used to use multiple edges of the clock and so appear to be much faster on paper (in a instructions per clock cycle way) but ultimately those devices always had lower maximum clock (crystal) frequencies so ultimately difference was much lower. This link has a reasonable description: en.wikipedia.org/wiki/Cycles_per_instruction
My name is of no consequence.
My name is of no consequence. Жыл бұрын
what are you considering a "machine cycle"? i don't know much about the 68hc11, but one of the things that made the 6502 so awesome was one machine cycle (aka doing a thing, whether that thing be a memory fetch, instruction decode, alu operation, whatever) happened in one clock cycle- there was even a tiny bit of pipelining, though i'm fuzzy on the details- i think a memory fetch would happen in parallel with the instruction decode, so if an operand was needed it was already there by the time the instruction was ready for it. so a 2mhz 6502 was actually pretty close to a 8mhz 68k (at least in terms of fetching and executing instructions, ignoring differences in complexity of those instructions...)
Kevin Olive
Kevin Olive 2 жыл бұрын
when I was in college (1980s), the assembler class I was taking didn't include how to do output but instead had us dumping memory [to paper] and then highlighting and labeling the registers, and key memory locations. I do recall reading files at some point because, due to a bug, I corrupted my directory structure and lost access to my home dir. Thanks to a brilliant Lab Tech (he was like 14 or so and attending college), my directory was restored. I couldn't say if that was from a backup or if he fiddled with the bits to correct the directory but I'm pretty sure it was the former.
Wafikiri
Wafikiri Жыл бұрын
I spent four years programming a 6502. One of my last application versions would overflow the 2K EEPROM by one byte, but I could manage to shrink the last program by one byte.... by modifying a jump back so that it jumped to the previous byte, which was the second byte of a 2-byte instruction but happened to be the right opcode I needed next! This chancy patch let me deliver the application without a thorough revision of programs to find out whether I could squeeze a byte off one of them. For subsequent versions, I had to modify the hardware and two 2K EEPROMs were installed in the one EEPROM socket, one above the other, all pins but one (the strobe pin, there working as the 2K-page address bit) correspondingly soldered together.
Dave Poo
Dave Poo Жыл бұрын
Those crazy days when you were just trying to save a single byte! Nowadays, i could leak a gigabyte of memory and probably not notice.
Cigmorfil
Cigmorfil 2 жыл бұрын
14:00(ish) The 6502 does no initialisation itself outside of using the vector ($fffc) to provide the code to start executing (and set the I-flag): its registers (and memory) can only be assumed to hold random values (except the I-flag which is set to prevent any IRQs) - it is up to the start code to set whatever is necessary, eg clearing memory. The first instruction of the called program should be to [re]set the stack pointer: LDX #$FF (or whatever value the system designer wants during reset), TXS.
Bruce Hoult
Bruce Hoult 2 жыл бұрын
I was just about to post this. He shows 6502 code in the rest routine to initialize the stack pointer and decimal flag, and proceeds to hard code this into the power-on reset hardware sequence instead. Just wrong.
cogwheel42
cogwheel42 2 жыл бұрын
Memory-mapped I/O is still very much in use. A large amount of memory address space on a modern PC is used, e.g., by your video card, which is why 32-bit windows would only have ~3 GB available to applications on a system with 4 GB of RAM installed.
Dave Poo
Dave Poo 2 жыл бұрын
I suppose it is, however nowadays its all virtual memory. You don't even know where in RAM you actually writing to.
Mike Young
Mike Young 2 жыл бұрын
Pretty cool. I did the same thing back in the early 90s using Borland Turbo C++ 1.0. I based it on the book 22 microcontroller project you can do at home, or something like that.
Nick Tzi
Nick Tzi 2 жыл бұрын
my thesis has to do with writing a 8085 emulator and i find your videos really useful! you earned my subscription! keep it up :)
Man of Norse
Man of Norse Жыл бұрын
My first CPU emulator in C was for a configurable VLIW-CPU back in the mid/late 80s ;) and that was not considered to be enough for my thesis .... Where do you study? ;)
Nick Tzi
Nick Tzi 2 жыл бұрын
@Dave Poo that's pretty obvious! but your approach is really user friendly and easy to understand, so probably i'll stick to it for now.
Dave Poo
Dave Poo 2 жыл бұрын
Thanks, i've never written a CPU emulator before so this is me going through the process. There are probably many different ways to do it.
Richard Feynman
Richard Feynman Жыл бұрын
In the early 80s I wrote several little programs for the 6502 in assembly language, just for fun, on my Apple II. It was always amazing how much faster this was than the same program in Basic language. The 6502 was really a simple design and easy to understand.
Dave Poo
Dave Poo Жыл бұрын
Yeah, i agree a very well designed and very successful processor. A marvel of the modern age.
Archfile X
Archfile X 2 жыл бұрын
Very impressive and most interesting. My C/C++ is pretty rusty, but most of this made sense, I'm keen to play with this idea
Martin Stent
Martin Stent Жыл бұрын
I actually wrote a 6502 emulator in C on my Atari-ST (68000 CPU) in 1987. I was quite proud of it. It used a kind of jump table for the actual instructions. I made an array of pointers to functions, and used the content of the instruction register as an offset into this array to call each Op-code function. For example at A9 was a pointer to the LDA immediate mode function. I started off writing a cross-assembler, and then wanted to test the resulting machine code and so wrote the emulator for it. Amazingly, after all these years I still have the source code!
GADONK!
GADONK! 3 ай бұрын
WOW i literally did the same thing. With this jump table you reduce the fetch-decode-execute cycle to O( 1 ) instead of O( n )
Tigrou7777
Tigrou7777 Жыл бұрын
@Martin Stent thanks for sharing, you should put this in a github repository, not in youtube comment
Andrija Jovanovic
Andrija Jovanovic Жыл бұрын
thank you :D
Martin Stent
Martin Stent Жыл бұрын
@Andrija Jovanovic /* a 6502 simulator and assembler in Lattice C M.Stent Dec. 1987 */ long *jt[256]; /* jump table */ unsigned char memory[0x10000]; /* cpu registers */ unsigned char a; /* accumulator */ unsigned char x; /* index reg x */ unsigned char y; /* index reg y */ unsigned short pc; /* program counter */ unsigned char sp; /* stack pointer */ unsigned char p; /* status reg */ unsigned char ir; /* instruction register */ unsigned short fpaddr; /* front panel address reg. */ unsigned char fpdata; /* front panel data reg. */ unsigned short ruaddr; /* run stop-on-address */ unsigned char ruinst; /* run stop-on-instruction */ int ruclk; /* run stop-on-clock */ /* definitions for status reg. p */ #define CMASK 1 #define ZMASK 2 #define IMASK 4 #define DMASK 8 #define BMASK 16 #define VMASK 64 #define SMASK 128 /* inverse masks */ #define NCMASK 0xfe #define NZMASK 0xfd #define NIMASK 0xfb #define NDMASK 0xf3 #define NBMASK 0xef #define NVMASK 0xbf #define NSMASK 0X3f long time; /* cpu clock */ int clock; /* display clock */ /* here I leave out a lot of stuff connected to the display on an Atari ST but here is the core of the matter...*/ void execute(func) void (*func)(); { (*func)(); } void init_jump_table() { void adc(),and(),asl(),bcc(),bcs(),beq(),bit(),bmi(),bne(),bpl(); void brk(),bvc(),bvs(),clc(),cld(),cli(),clv(),cmp(),cpx(),cpy(); void dec(),dex(),dey(),eor(),inc(),inx(),iny(),jmp(),jsr(),lda(); void ldx(),ldy(),lsr(),nop(),ora(),pha(),php(),pla(),plp(),rol(); void ror(),rti(),rts(),sbc(),sec(),sed(),sei(),sta(),stx(),sty(); void tax(),tay(),tsx(),txa(),txs(),tya(),xxx(); /* 65c02 */ void bra(),phx(),phy(),plx(),ply(),stz(),trb(),tsb(),bbr(),bbs(),rmb(),smb(); register int i; for(i=0;i
Martin Stent
Martin Stent Жыл бұрын
@Andrija Jovanovic Well, 34 years on, I expect there are quite a few embarrassing things about it, and remember, a state-of-the art 68000 Lattice-C from 1987 is going to have issues. But here goes...
Nallid
Nallid Жыл бұрын
Nicely done. A better and simpler way than when I wrote a MIPS simulator.
Stepan Rogonov
Stepan Rogonov 2 жыл бұрын
are you kidding me? that's a "holy grail" over all the KZbin for the people who studying a CS. Dam, u r an amazing person!
CallousCoder
CallousCoder 9 ай бұрын
@Active Low 🤣nice one 😄I especially adore the goto. Although I’m not one of the “goto must go too” people. In C there’s a definite justifiable need for it. Just not this one 🤣BTW, I see that you are a Synth Freq too!
Active Low
Active Low 9 ай бұрын
@CallousCoder The best way to do it is to say, "if (opcode == 0xWhatever) callWhateverHandler();"... and put all 256 of those in a row inside your main() function. Then have each function do a goto back to your main(), or something like that.
CallousCoder
CallousCoder 11 ай бұрын
@Dovi Salomon and that still doesn’t mean it’s small, efficient, clean and an easily maintainable solution 🤣 And when you go the (c)Python is not something to aspire to :) Slow inconsistent language. It started off so well with a very good architectural principle: “theres only one way to do something” but they’ve lost rust somewhere around 2.7. And my god it’s slow…
Dovi Salomon
Dovi Salomon 11 ай бұрын
@CallousCoder cpython uses a giant switch statement for opcode dispatch
CallousCoder
CallousCoder Жыл бұрын
@Brad Allen incrementing PC can be part of that same map, either as an argument or a dedicated secondary function you call :)
David Spector
David Spector 2 жыл бұрын
I love the 6502 instruction set. Make you very handy at packing code into small pages for efficiency.
Monkey see, monkey do
Monkey see, monkey do Жыл бұрын
Thank you so much, Dave! It's exactly what I have been looking for!
Black Hermit
Black Hermit Жыл бұрын
The beginning of 6502 is the ray. Forget not the humble beginnings from which the registers and stacks of our Times were conceived. Emulating it in C++ is so cool.
von Blankenburg – Gaming in Strategie & Wirtschaft
von Blankenburg – Gaming in Strategie & Wirtschaft 2 жыл бұрын
Pretty awesome stuff! Thank you for all the effort you put into this!
Cigmorfil
Cigmorfil 2 жыл бұрын
29:00 $fffc is a vector, so if those bytes are loaded there the 6502 will load the PC with $42a9 and try to execute that memory, which contains $00 (BRK) at the moment.
Dave Poo
Dave Poo 2 жыл бұрын
Yep, i never got round to handling the reset vector correctly.
jonnoMoto
jonnoMoto Жыл бұрын
Look like fun. Learnt programming with a 6502 and opcodes as part of my EE degree. Was thinking of replicating a 6502 on a nexys2 I've had knocking about for ages. Might do it in rust first.
laka
laka 2 жыл бұрын
You might wanna take a look at the header. It defines portable integer types of fixed width.
Delphic Descant
Delphic Descant 2 жыл бұрын
Definitely. I was surprised to see "unsigned short" and whatnot, but I guess it's probably an "old habits" sort of thing.
P. Wingert
P. Wingert 2 жыл бұрын
Love this. When I was in University in the 1980's, we had to write a microcode engine to implement instructions for the 6809 and get a simple program to execute on it. We had to write the microcode for each instruction. We were given the microcode instructions for the RTL Register transfer language. You could create a microcode engine that could then run any instruction set on top of it! Set the microcode engine up as a state machine to make life a bit easier. At the time we were actually using an IBM/370 and the VM operating system so we each had our own virtual machine. but the microcode engine had to be writeent in 370/assembler and boot as a virtual machine on the mainframe! These days the average PC is capable of this with relative ease!
P. Wingert
P. Wingert 2 жыл бұрын
@Dave Poo The best part was we never realize that this super special virtualization technology would become so prevalent back then. It was just what we had to use to get the assignment done. We never sat back and thought about just how much power we had or what would happen to it!
Dave Poo
Dave Poo 2 жыл бұрын
Great story. Yeah, not only an average PC is capable of this, but even a below average smart phone could emulate this now. I think it's amazing that we now all walk around with super-computers in our pockets and totally take it for granted.
Pellervo Kaskinen
Pellervo Kaskinen 2 жыл бұрын
Regarding I/O, Motorola 6800 series ans 68,000 series, as well as the 6502 you are building the emulator, had all I/O mapped to a range of memory addresses. The Intel system was based on a bank aside memory. But apart from the one line to select the I/O bank, I assume the practice ends up being pretty much the same. I recall the PC addressing of I/O in the DOS era, before the Windows began to hide them with their virtualizing (I believe is the term). However, my intention was to point to Rockwell R65F11 "Forth Engine". It had basically the 6502 core, with some ROM added to the chip. And the ROM contained a Forth kernel. I still have a board that I assembled around the R65F11. Don't know though, where it is now. When we moved from our analog controllers to computerized ones, the first version was with Motorola 6800, running just on a 1 MHz clock. By that time I knew basically nothing of computers and I asked our computer guru why we used such slow chips as Intel for example had their 8031 running at 12 MHz clock. I got a laugh and a lessiion about how many clock cycles each chip needed per typical instruction. By the time we went to the next generation with Motorola 68,000, I knew a bit more and had built that mentioned R65F11 based, as well as started understanding a bit more about computers in general.
Dave Poo
Dave Poo 2 жыл бұрын
Yeah it was funny, You had the 6502 running at 1Mhz and it could Add in 2 cycles, and you had the 68000 running at 7Mhz and it would Add in 8 cycles. So the 68000 wasn't 7x faster than the 6502 (at least for 8-bit math). It goes to show if you have 2 different CPU architectures, you can't just look at the clock speed to know how fast they are, you have to take everything into account.
Dactyl Pilot
Dactyl Pilot Жыл бұрын
Back in the 80's I purchased a computer kit from a company named "Southwestern Technical Products", out of California. It was the first and only computer I ever built. Had to solder every component (capacitors, diod's , resister's, and even the ram chips, a whole 4k worth. It took about a month to get it all done. I never built another computer since.
Ruth Moreton
Ruth Moreton Жыл бұрын
This video also highlights a difference between emulation and simulation. If the CPU were being Simulated you'd not need to tell the program how many clock cycles an instruction takes and count them down. It simply WOULD take those number of cycles as you'd be simulating the actual logic gates in the CPU itself responding to a clock pulse, rather than programmatically returning what the instruction should do.
Vyratron
Vyratron 2 жыл бұрын
I've done something like this before, just watched to see how someone else would do it. Instead of writing code for every instruction you can find a pattern in the bits of all the instructions, and make lookup table to indicate which instruction and addressing mode and flags and cycles are used. Then you don't need code for every instruction. That's how the real chip actually works I think.
Anders Jackson
Anders Jackson 2 жыл бұрын
@Dave Poo I would probably argue that it is easier to see the pattern on an early 8-bit CPU like 6502 then on a 68k, Intel 8086 etc or a modern RISC V CPU. ;-) (Even though the RISC V is a orthogonal CPU design, like 6809, 68K and PDP 11. Real nice CPU to code machine code in. :-) )
Dave Poo
Dave Poo 2 жыл бұрын
Yep, and i think if i was emulating a more complex CPU then that would definitely be the way to go.
Vyratron
Vyratron 2 жыл бұрын
There is a table of instructions here, and maybe you can see that most of them are organized in a pattern, just a few look out of place. www.masswerk.at/6502/6502_instruction_set.html
Chris bey
Chris bey 2 жыл бұрын
Very cool, taking a cpu architecture class right now and an advanced c++ class. Perfect for learning :D
GordieGii
GordieGii Жыл бұрын
Very enlightening. FFFC would be in the boot ROM, wouldn't it? You wouldn't actually need a video card, just a memory mapped set of eight LEDs and eight (or 24) toggle switches and a couple push buttons, or a UART.
Patrick Concannon
Patrick Concannon 2 жыл бұрын
What a great video. Thanks a lot for creating and sharing!
Cigmorfil
Cigmorfil 2 жыл бұрын
41:46 The endianess of the host is irrelevant unless you are using non-char types to read or write the "memory" (an array of char). On a bigendian system the memory byte array is still going to be in little endian form - it is just a byte stream. If you were going to use the endianess of the host cpu I would expect you to cast the memory from char to word and then read/write the word in one go.
Dave Poo
Dave Poo 2 жыл бұрын
Yep, i got rid of that comment later
RishiNiranjan
RishiNiranjan 7 ай бұрын
Thanks Dave. Im a fresher just started working as a softare developer . This is a very interesting side-project. Thanks for sharing it.
RishiNiranjan
RishiNiranjan 6 ай бұрын
@Kartik Anand nope
Kartik Anand
Kartik Anand 6 ай бұрын
Is that link in the description working for you?
Christian Lohmann
Christian Lohmann 2 жыл бұрын
Remember I had done similar some 30 years ago on Atari ST with writing a cross assembler and emulator for 6502 and partially Z80. Was fun. And agree, is quite a learning experience.
Christian Lohmann
Christian Lohmann 2 жыл бұрын
@joebonsaipoland It started ‘just of fun’ as I needed a 6502 assembler back those days. And ended like it. I was in contact with a German computer Magazin back in those days for an article but that went nowhere. Most important I quit my job as programmer and started studying economics and freelanced as programmer. Those immediate payed assignments took priority.
joebonsaipoland
joebonsaipoland 2 жыл бұрын
Was it a commercial product? Or just for fun?
Dave Poo
Dave Poo 2 жыл бұрын
You emulated the 6502 on the Atari ST! that's pretty hardcore for the day.
Night Fox
Night Fox Жыл бұрын
This is really cool. I think I'll try this myself. One idea I had that I think will make things a lot easier is to make an array of void pointers for the instructions which you can then assign to addresses of c++ functions that you write to do what each instruction needs to do. Then all you need to do is call the function at that address. Since all of the required information is going to be at the program counter, you can just have the function itself grab the data it needs, advance the program count, and decrement cycles accordingly. You could actually then stub out every instruction available to the 6502 without actually implementing them and just implement them one by one, only having to fill in the function and nothing else.
Night Fox
Night Fox Жыл бұрын
@Dave Poo So after playing around with this a bit, I decided the best way to do this may actually be to create a control word lookup table. Then I only really need to write functions for the instructions that use the ALU. I've starting writing to table and handling for microcodes in my CPU class but I'm trying to keep the control word to 16 bits and I currently have 19 flags I'd like to have. I can't seem to find any documentation on how the 6502 handles control logic only that it apparently handles microcodes "differently than modern cpus" (Yeah the article I found was very vague about that). Any ideas on how I might be able to handle this? Personally the switch statement approach is already too cumbersome for my liking and the cycle counts already add quite a bit to that complexity so I'd like to avoid it if possible.
Dave Poo
Dave Poo Жыл бұрын
Yep, that's one way to do it but it wouldn't be much different to the switch statement in the end. You could create function pointers to the relevant addresssing mode and cycle counts and the actual instruction and then decode the instruction and lookup the correct functions to call. If i was to implement a more complex CPU (such as the 68000) then this would be the way to go as the number of addressing modes and registers goes up dramatically, which would make the switch statement approach too cumbersome.
Edwardian Steam Works
Edwardian Steam Works Жыл бұрын
Surprisingly it worked despite a bug in FetchWord with cycles++ when it should be cycles- - Also you should implement the instruction vs mode table to simplify it dramatically. By masking on the opcode bits you can then use a switch statement for the addressing mode. It would reduce the combinations to 23 instruction switch statement and 8 addressing functions. Btw the pc++ wrapping to 0x0000 is legal so as long as mem is mem[16k] it’s fine. I hope this isn’t taken as armchairing. The video was fun to see.
Dave Poo
Dave Poo Жыл бұрын
Yeah, i was confused at it working as well, but that shows why thorough testing is required of even the simplest of programs.
RVA
RVA 8 ай бұрын
This is an awesome series! I did a similar exercise only I used Rust and this was a huge help. Thanks!
DBZM1k3
DBZM1k3 2 жыл бұрын
Really interesting! Thanks for the video. I see people have mentioned about std::uint8_t and std::uint16_t, but in C++17 onwards there is also std::byte in the cstdef header which you can use. It also can be passed to a non-member function std::to_integer(std::byte b) for a more numeric output if you're debugging the byte values.
Corn Pop
Corn Pop Жыл бұрын
Interesting..I wonder if it fixes the longstanding issue in C++ where std::cout
Venturi Life
Venturi Life Жыл бұрын
Love it. I can't imagine being this clever.
minastaros
minastaros 2 жыл бұрын
5:20 There is header file which defines precise types like uint8_t, uint16_t instead of things like "unsigned short".
Jedzia Dex
Jedzia Dex 2 жыл бұрын
@272zub Thanks for the reply. Now this is worth reading:) But you got the point I made? The distinction between real hardware and the emulated registers/emulated machine. In this case you HAVE to be precise with the exact size of data-types or it is a bad awakening switching environments. There is no lamenting around and no excuses in this case:)
272zub
272zub 2 жыл бұрын
@Jedzia Dex Hold your horses. :) I think you missed the "where it's not needed" part in my reply. If you need a fixed-size integer, e.g. a 16-bit unsigned integer, then by all means do use cstdint (or stdint.h when in C). It's so much more better than either using an unsigned short because it happens to be 16 bits on your platform, or than making your own half-baked stdint. Clearly when writing an emulator, like it's done in this video, you will often needs such fixed-size types. I am not disputing that at all. In reality the types from cstdint are just the correct typedefs to some of the "normal" integer types, e.g. on some platforms it is that uint16_t is a typedef of unsigned short. So using the uintNN_t type of course is exactly the same as using the correct "normal" type. What I didn't like was @minastaros' suggestion to use the fixed-size types everywhere. In the extreme this means don't use an int at all, always use (u)int_NN. And that is where my "less efficient" comment applies: godbolt.org/z/7obs5s - if you use uint16_t when you don't explicitly need it, and an int would have been a good choice - you can see that the 16bit version is actually more complex than the 32 bit one. And that the normal int version is the same as the 32bit one. And by "Miserable" standard, I didn't mean the C++ standard. I meant en.wikipedia.org/wiki/MISRA_C and especially it's C++ evil cousin, which, to me, is how C programmers, who don't know C++, get their revenge on C++ programmers... By the way there are also the types (u)int_fastNN_t and (u)int_leastNN_t which could offer the best from the both worlds: Guaranteed minimal size while still being as efficient as possible. As their size is not guaranteed, that can't be used when a specific memory layout is needed though.
Jedzia Dex
Jedzia Dex 2 жыл бұрын
​@272zub Ah yeah? Proof that! This mechanism is to aid in platform independent programming, because it is in fact NOT standardized and machine dependant what Dave uses. These storage modifier keywords are platform dependant. What you call "Miserable" (uint_8t, etc.) does in fact translate to the same instructions on Dave's machine. So your efficiency claim is a fallacy. It is just good style to use them. Especially when emulating foreign hardware. For example, look at the code from the professionals at github.com/stuartcarnie/vice-emu/blob/master/vice/src/arch/XXX/types.h. They have to define for EVERY system what to use. That was a design decision from the start and that project is very mature. In contrary look at the very new github.com/commanderx16/x16-emulator project. They use proper platform-independent code and save a huge amount of code. @272zub you can't generalize it this way. If this facility is there, why don't use it? What you say is an edge case and is only true in special cases. In addition, what you tell affects the code running on the (compiler-)TARGET. But here the function of this facility is a data-type related to the emulated machine (on the HOST). You are wrong on several levels. See stackoverflow.com/questions/6144682/should-i-use-cstdint ... I think that is what is related to your thoughts and what doesn't apply here. minasteros: #include ... Its C++ :) en.cppreference.com/w/cpp/header/cstdint
272zub
272zub 2 жыл бұрын
@minastaros ... and if you use it where it's not needed (just because a Miserable standard says so), you just make the code less efficient.
minastaros
minastaros 2 жыл бұрын
Yeah, some industrial coding standards actually require using it. If you make it a habit, your code will always be portable between different architectures - at least concerning POD type sizes.
Ed Developer
Ed Developer Жыл бұрын
1 hour video. I can say I finally learned how computer works. thank you so much
Ameen Altajer
Ameen Altajer Жыл бұрын
Your awesome man, keep your content coming!
Gary K
Gary K Жыл бұрын
Seriously impressive video and very informative. And someone who actually does know C++.I've been around software dev alooong time and when asked 'do you know any C++ devs' I always reply 'I know quite a few who *claim* to be c++ devs'. The rarest of beasts I think.
George Tsiros
George Tsiros 3 ай бұрын
I would argue that there is no person on the planet anymore that can truthfully say "I know c++", considering the language isn't even _designed_ by one person anymore. Even making the question more constrained, eg "do you know the _syntax_ of c++?", even then, the answer will always be "no". Besides, what is the point of asking such questions when there is no reference, 100% compliant, verified, compiler? (there exists exactly one verified _c_ compiler that supports _almost_ all of c11)
Baruch Ben-David
Baruch Ben-David 2 жыл бұрын
6502 was the first chip I ever programmed. I had the most fun with it. Good memories...
P. Wingert
P. Wingert Жыл бұрын
@Anders Jackson I'm so jealous! So much memory! I worked from a ROM Monitor! I would love to play with OS9! I loved the PDP 11/34. I got to work with it in university then on to a VAX 11! The Vax 11 was the size of a desktop full tower compared to the PDP 11/23 which was like a couple of fridges side by side! It was an order of Magnitude faster too. I now run PDP OS on a Raspberry PI 4 simulator at four orders of magnitude faster and the size of a matchbox!
Tom Draug
Tom Draug Жыл бұрын
Same here
Anders Jackson
Anders Jackson 2 жыл бұрын
@P. Wingert yes, they even simulate a PDP 10 (that I used) in an old RPi model (don't remember which). That original PDP 10 was heating up a huge building. :-) Also on Update web site.
P. Wingert
P. Wingert 2 жыл бұрын
@Anders Jackson You can now simulate it at an order of magnitude faster on a Raspberry Pi 4 and save on electricity costs. Best Part its portable!
Anders Jackson
Anders Jackson 2 жыл бұрын
@P. Wingert I got in contact with PDP 11 at university computer club (www.update.uu.se/). I hacked on their PDP 8 and fixed the serial port IN the CPU. We had PDP 10 and it is still running (virtually) and a PDP 11 where a friend of mine wrote an IPv4 stack. And I did some small hacks for some tools to it. Yes, the PDP 11 was nice.
Erwan
Erwan Жыл бұрын
Hi, very nice video. I wish we could copy a real sample of bytes code from somewhere, execute it in the simulator and see the result :) (hmm just saw you did multiples video) Also, correct me if i'm wrong but FetchWord() should do Cycles -= 2 and not += 2, right ?
Dave Poo
Dave Poo Жыл бұрын
Yeah, i do get to a test program in the end. But there is no point trying that until you think you have all the instructions implemented. Yes i got that cycle count wrong, it is fixed in a later video.
LMacNeill
LMacNeill Жыл бұрын
And now we all have a *hugely* greater appreciation of the folks that have written the C64 emulators and NES emulators and PS1 emulators that we run on our RetroPie machines! :-)
Dave Poo
Dave Poo Жыл бұрын
Yeah, those emulators are usually the collective work of quite a few poeple
Frank Beans
Frank Beans Жыл бұрын
Cycles should be while(1) not specified in parameters, as a CPU will free run, unless you step the clock. Also, Fetch doesn't get "data" it gets "OpCode"s.
Brandon Allen
Brandon Allen Жыл бұрын
I took a class where we have to write an emulator for a processor very similar to the 6502. The fail rate for the course is around 30%
Dave Poo
Dave Poo Жыл бұрын
30% can't do the maths right, the other 80% are fine with it.
G C
G C Жыл бұрын
The 6502 is one of the best processors to learn on. Nice and simple and covers most of the concepts.
Dave Poo
Dave Poo Жыл бұрын
It is simple, considering that it's actually a complex instruction set processor.
Harry de Kroon
Harry de Kroon 2 жыл бұрын
Really nice to see how you've set this up. Clear coding. Just wondering about the clock: When you model the system clock as a separate entity, it could remove the Execute method from CPU. Just let the clock run, optionally for a fixed number of ticks. Instead of the cumbersome counting of Cycles, subdivide each instruction in Steps and push those on an internal stack in CPU. That would make it possible to halt the CPU for inspection. But yeah, it is not full implementation :-)
gsck
gsck Жыл бұрын
the interrupt vectors dont actually get executed when they get triggered, but instead go to the address that they point to. So for example, when you were testing LDA ZP, you have 0xFFFC as 0xA5 and 0xFFFD as 0x42, the processor after resetting would real that and set the program counter to 0x42A5 and then begin program execution.
1294698
1294698 Жыл бұрын
Crazy idea. Interested to see how it turns out.
LordMardur
LordMardur 4 ай бұрын
41:20 If you want your 2 byte value returned from "FetchWord" in your machine endian, you don't have to swap anything. The operation "first_byte + second_byte * 256" will always convert the little endian from the memory array into your machine endian, no matter what your machine endian is. If you instead "memcpy(memory + PC, &Data, 2)", then you have to swap, if your machine is not the same endian as the memory array.
Dave Poo
Dave Poo 3 ай бұрын
I think that was fixed in a later video in the series
Proxy
Proxy 2 жыл бұрын
I did something similar to learn about the 6502, specifically the 65C02. but i didn't write an emulator, i built the whole CPU in a Logic Simulator. the end result is the same, you get a better understanding of the hardware. and it was quite fun.
Proxy
Proxy 2 жыл бұрын
@Dave Poo thanks. something a bit more direct to the video: around 5:13 why did you define a byte and a word instead of just using uint8_t and uint16_t? the "_t" types are made to be universal across all C/C++ compilers and architectures. also, the endianness of the platform shouldn't matter if you just have 2 temporary 8 bit variables instead of a single 16 bit one. and i assume in later videos you fixed the thing where the CPU starts executing from 0xFFFC? because that's not where the PC starts at, but rather at 0xFFFC and 0xFFFD is the address that gets loaded into PC before the CPU starts executing. it's like a hardwired jump indirect. either way your video made we want to try this for myself as well, but i'll try it in C instead of C++.
Dave Poo
Dave Poo 2 жыл бұрын
Pretty cool, good work.
topquark22
topquark22 Жыл бұрын
Why not implement each instruction's "microcode" into the Instruction class itself. Make Instruction virtual, so each one is a subclass of it. Object-orientation is the beauty of C++.
Peter Ashwood-Smith
Peter Ashwood-Smith 2 жыл бұрын
Good memories - one of my first CO-OP jobs after my 2nd year of comp sci was to create a Z80 emulator for an aerospace company. I seem to recall it being able to run about 1000 instructions a second .. back in the dark ages ;) You are right you really learn... I still remember the DAA instruction .. god...
Peter Ashwood-Smith
Peter Ashwood-Smith 2 жыл бұрын
@Dave Poo It was used by that company to test their software before moving it onto real hardware. I dont know if they ever sold it. Was a fun project, my supervisor just left me alone and I gave a demo every friday to the team. Wrote a users guide when I left and had an office overlooking Vancouver from Burnaby Mountain.
Dave Poo
Dave Poo 2 жыл бұрын
Was the Z80 emulator you did put into use in a product?
Alain Fauconnet
Alain Fauconnet 2 жыл бұрын
Fascinating video, really! I'm impressed by how fast you are able to think, design then write clean and modular code. Thanks for this. I've learned writing machine code on the 6502 (KIM and SYM boards) as a student. I've had several 6502-based machines after this (Acord Atom, Oric 1 & Atmos...). This CPU was pretty much everything to me in my 20s. Just wondering... unless I'm missing something, shouldn't it be "Cycles -= 2;" instead of += in FetchWord()?
Dave Poo
Dave Poo 2 жыл бұрын
Thanks. 6502 was so popular, there are many with fond memories and their own stories. I can't take all the credit for thinking fast as I decided to edit the videos to make them more watchable, quite often "thinking time" has been edited out. I think it is actually important to stop and think about what you are doing as doing stuff as quick as you can isn't always the best or quickest way to get the job done. As for the "+=", yeah i think that's a mistake. I just had a quick look at the current source code and that was corrected at some point. It does also maybe explain another comment on this video pointing out that I pass in 9 cycles at the end to run the little program and it only needed 8, so there must have been a bug somewhere. The unit testing starts in the next video, so the code gets better as is evolves (which was one of the reasons i wanted to do this series, to show that the code you finish with isn't always the code you started with).
Tim Hosking
Tim Hosking Жыл бұрын
When you push onto the stack you actually decrement SP. The stack sits above the heap and grows downwards towards it. I guess for the purposes of this exercise it doesn’t matter, but if you want a true emulation that emulates the real CPU accurately...
Tim Hosking
Tim Hosking Жыл бұрын
@Dave Poo 👍 As you are working with 8-bit opcodes, have you considered simply using a lookup/jump table containing function pointers, cycle count etc., instead of that switch statement? Better still abstract each instruction into a generic class.
Dave Poo
Dave Poo Жыл бұрын
Correct! and I got around to fixing it in#8 kzbin.info/www/bejne/n2ath3Z-iLOrgLs
Mostly Penny Cat
Mostly Penny Cat Жыл бұрын
I've started doing this exact same thing but in VHDL for an FPGA.
Viktor Hugo
Viktor Hugo 2 жыл бұрын
Your Video is very interesting but the vectors from MOS6502 are two bytes address long and for absolute jumps reserved. program starts at this address or the interrupt routine, NMI or IRQ FFFA/FFFB, FFFC/FFFD, FFFE/FFFF are NMI, RESET, IRQ (low7high) start points are setting the stack pointer and disable the interrupt , and end of the interrupt routine you have the instruction RTI(return from interrupt) the vectors are located in ROM or Flash memory because the CPU must find this at reset. (you have to think of 4C instruction (jump) before the vectors)
Mechael Brun-Kestler
Mechael Brun-Kestler 2 жыл бұрын
When implementing parts of a register its a good idea to wrap that into a struct
Glib Bond
Glib Bond 2 жыл бұрын
Why not use int16_t/uint16_t (from "cstdint") for the 16 bit register? There is no guarantee that "short" is 16 bits long :-) And how about uint32_t for the 32 bit long int? (I see that you've reacted to similar suggestions, but I'll just leave it here)
Johnny Ragadoo
Johnny Ragadoo 2 жыл бұрын
In the 90's I wrote a generic screen display that would show memory and registers as defined in an external library. Then, I wrote external libraries for 6502, Z80, 6805, and 6811 processors. A retargetable cross debugger, in other words. Very handy. I could load s-record or Intel hex files, single step, run and set breakpoints, all kinds of stuff. When you're too poor to afford in-circuit emulators, things like software emulation can be quite useful.
Johnny Ragadoo
Johnny Ragadoo 2 жыл бұрын
@Dave Poo It was more out of desperation. :-)
Dave Poo
Dave Poo 2 жыл бұрын
You must be some kind of wizard
Thomas Herpoel
Thomas Herpoel 2 жыл бұрын
Nice video ! In the execute fonction, as Cycles is an unsigned variable, what happens if it goes below zero ? It could happen since you don't check the value after each "fetch" fonction call. I guess Cycle will equal to 0xFFFFFFFF, which is way bigger than 0 in an unsigned variable, and the loop will continue for a long time.
Thomas Herpoel
Thomas Herpoel 2 жыл бұрын
@Dave Poo I'll check your other videos then. Thank you for producing such quality content!
Dave Poo
Dave Poo 2 жыл бұрын
Yeah, i messed that up, it gets changed to signed in a later video i think.
tactileslut
tactileslut 2 жыл бұрын
Once the emulator and the microcontroller running it can fit on a 40-pin DIP all sorts of old hardware can live again. :)
unmountable boot volume
unmountable boot volume Жыл бұрын
That wouldn't be very useful on the 6502, because they are still in production. For other CPU's though, it would definitly save some PC's.
Dave Poo
Dave Poo 2 жыл бұрын
People do make drop in replacement emulated SID chips (SwinSID)
Cigmorfil
Cigmorfil 2 жыл бұрын
16:15(ish) The 6502 uses synchronous memory access: *every* clock tick accesses memory. To avoid memory clash with, say, video hardware, the cpu would access memory during the rising clock edge and the video hardware would access on the falling edge (which is the rising edge of the inverted clock pulse the cpu generates) - you just need memory twice as fast as the cpu.
Cigmorfil
Cigmorfil 2 жыл бұрын
@Dave Poo Possibly...I only have the workings of the PET to hand - never used a C64. The Acorn Atom didn't do this so you'd get "snow" on the screen unless you waited for fly back before writing to screen memory.
Dave Poo
Dave Poo 2 жыл бұрын
Does this explain the "sparkles" on the VIC-II in the C64. When the VIC-II would heat up it would start to create the sparkles on the screen because the memory timing on that chip was off.
Caio Molinari
Caio Molinari 2 жыл бұрын
Man, that's really amazing, great job.
Robert Topper
Robert Topper 2 жыл бұрын
The first job my son had at Intel (15 or so Yrs ago) was debugging cpu design by emulating hardware with software
xybersurfer
xybersurfer Жыл бұрын
@myst snake indeed. only to discover that they made a mistake in the specifications
myst snake
myst snake 2 жыл бұрын
of course that only works if the software(usually written to the documentation) works as the actual hardware does, certainly not the case with the original 8086, the manual perfect 86 chip clones weren't perfect to the actual intel chips. not a enviable position imo, to try and insist to the hardware lot they messed up as i can fully see they would swear black and blue its the emulator that's bugged.
Jari Sipilainen
Jari Sipilainen 2 жыл бұрын
did with amd haha
Siminfrance
Siminfrance 2 жыл бұрын
Very interesting ... busy making my way though all the emulator videos, very nice indeed. Well done and well commented as well.
Dave Poo
Dave Poo 2 жыл бұрын
Thanks, i think i said in this first video somewhere that i wasn't going to write the whole CPU emulator, but in the end i went through and started doing the whole thing. I think one of the main purposes is to show that when you are writing a program, what you end up with is not always what you started with. The emulator code evolves and changes as the videos progress.
Jonatan Kelu
Jonatan Kelu Жыл бұрын
A Commodore 64 had a 6510 processor, not a 6502. The 6510 is mostly the same as a 6502 except it has some extra bank switching features.
Emmanuel SEJOURNE
Emmanuel SEJOURNE 2 жыл бұрын
Good to see you coding 6502, i made same thing during '80, with the 6502 itself on Apple ][. I have just an information, you should remove "Cycle" initialization, and "Cycle" control, because this is only the instruction that define the number of cycle to work.
Emil Steen
Emil Steen Жыл бұрын
Yes, that felt really backward.
SnapGotYou.com / Trossachs Photography
SnapGotYou.com / Trossachs Photography 2 жыл бұрын
Not sure if it is mentioned elsewhere, but having the Memory struct be responsible for managing the cycles in WriteWord doesn't feel right. The cycles are being spent/managed by the CPU. WriteWord would probably be better placed inside the CPU struct, as this is an abstraction you are placing on the memory during a CPU op to simplify the repetitive bit mashing you'll need to do.
Dave Poo
Dave Poo 2 жыл бұрын
In the end i regretted the cycle counting being done by individual parts of the emulation. If i did this again I would rather have had it just "know" the correct amount to take for the entire opcode and then also take any extras for instructions that did something strange. I did carry on with it though and get it working. It was just a bit more of a chore than it could have been, and more error prone i think
Folgee
Folgee 2 жыл бұрын
Just found you channel, awesome stuff my guy. You earned a subscriber
Zasbir Rahman Zayan
Zasbir Rahman Zayan 6 ай бұрын
I have completed this video and I have learned many things. Much support!!
Zasbir Rahman Zayan
Zasbir Rahman Zayan 6 ай бұрын
The instructions are simple to understand as well!!
DeLuxe
DeLuxe Жыл бұрын
This is absolutely amazing.
godzil42
godzil42 Жыл бұрын
A couple of (small) mistakes, if you really wanted to be accurate with the 6502: - S (the stack register) is actually 8 bit, not 16 - Pushing to the stack actually decrement it not increment - FFFC is containing an address, not code, so puting code here would not work on a real 6502 - Going further to FFFF as you store it in a 16bit integer will just warp around to 0, exactly like the real thing would do, so no need to check if you go further Oh and I see this is a series, so you probably fixed most of these I suppose :)
Dave Poo
Dave Poo Жыл бұрын
I fixed the stack in a later episode (see the pinned comment on this video). I never really fixed the FFFC being an address, but it wasn't really a big deal as i never got around to emulating a full system (including the correct boot up code for whatever system that would be). It's pretty funny, 35 videos and don't have a working system, i did kind of want to show that a lot of work goes into full emulation of system
Alex Novickis
Alex Novickis 2 жыл бұрын
ha! I wrote this program for 65816 back in 90's when I was interested in some aspects of snes workings. Really one thing I learned is all the addressing modes I didn't know about in 6502
Dave Roberts
Dave Roberts 2 жыл бұрын
The 6502 SP is a byte. It’s value is CONCATENATED with 0x01 to form the stack address. If the SP is 0xff and you increment it, it wraps to 0x00.
Dave Poo
Dave Poo 2 жыл бұрын
It was changed to a byte in a later video
Lofwyr
Lofwyr 2 ай бұрын
There are two bugs in this. You did not take the cycles by reference in WriteWord, so decreasing the cycle count is local to that method. You refactored two cycle decrements (cycles--) into one 'cycles+=2' in FetchWord. So, fetch word actually increases the cycle run count by two. and write word does not change the cycle count at all.
Francis Verhelst
Francis Verhelst 2 жыл бұрын
i remember from the old days, writing an assembler in machine code for the Z80. We wanted to compress it to a maximum, because of available memory (on a sinclair spectrum). Analyzing how the instruction code was built up on the bit level, we discovered the unofficial Z80 instructions before Rodney Zacks published his book "programming the Z80". To our surprise at that time the codes actually worked
Francis Verhelst
Francis Verhelst 2 жыл бұрын
@Dave Poo well, i am not shure of that. the instructions should be there folloowing the logic way of building the instructions on a bit base
Dave Poo
Dave Poo 2 жыл бұрын
I supose if you had pulled a different Z80 from another manufacturer then those illegal codes might have behaved completely differently?
Ruth Moreton
Ruth Moreton Жыл бұрын
the humble 6502 was used to create an entire gaming genre by Ian Bell when he wrote Elite.
Expedition Genesis
Expedition Genesis 2 жыл бұрын
Hey Dave! Thank you so much for sharing this. Instant subscriber! I'm working my way through this and adding a few of my own quirks here and there. Assembly language and retro programming is so fascinating to me. It's truly a lost art, like the basket weaving of technology. I'm getting started with 68000 SEGA Genesis development on my channel. Still in the baby phases but I hope to be consistent. Great work Dave!
Dave Poo
Dave Poo 2 жыл бұрын
@Expedition Genesis Yep, the MegaDrive was pretty cool. I think games programming is good for learning about computer hardware, as really you have know and program for the target hardware to get the most out of it. Even in todays world where the programmer is more abstracted away from the hardware (via OS and device drivers and Graphics API layers etc), you still really have to know the hardware you are targeting and if you don't you will probably fall flat trying to make a game (that works).
Expedition Genesis
Expedition Genesis 2 жыл бұрын
My channel is more about learning how computers work from a programmers perspective. I've always believed the best way to learn about computers, whether that be programming or even hardware, is through making games. Because games are exciting to make! I felt like the Mega Drive provided enough system resources to make some pretty powerful games while also being simple enough to understand it's architecture.
Dave Poo
Dave Poo 2 жыл бұрын
Thanks. Are you writing an emulator or a game for the Genesis (the "Megadrive" where i am)?
Miguel Gil Rosas
Miguel Gil Rosas Жыл бұрын
Terrific video! Thank you!
Yagami Light
Yagami Light 2 жыл бұрын
Hey man it's amazing can you do a series on emulating a 8085? It would be really helpful I am 17 years old and I've started studying microprocessors and I learnt 8085/86 this year
myst snake
myst snake 2 жыл бұрын
all cpu emulators work the same at the basic level, you just got to interpret the opcode right and then grab any extra bytes it requires, of hand i don't know the 85's instruction length, but for instance the 186+ decoding can be 1-15bytes, i believe the 8086/88 was't restricted, but the decoding will give that. find documentation that gives you the decoding of instruction, what the instruction actually does and the timings of each instruction plus the exact clock speed of whatever hardware you emulating (important). for instance the original pc had a clock same speed as ntsc 14 odd mhz, the cpu ran at a 1/3, the pic (i think) ran at 1/12. this is enough for a displayless emulator, in a real machine bandwidth may not go solely to the cpu (like a dma operation, cpu is't doing anything while it is)
Michel GRIGAUT
Michel GRIGAUT 8 ай бұрын
I think you missed that FFFC and FFFD was a VECTOR, meaning that the first intruction that the CPU will execute will be found at the addresse spotted by that value. If youy memory was full of zeros (which was a Comodore feature and not a 6502 feature - but ok) the the first intruction would start at address 0 as read from init vector. So you could simply hack your memory starting at address 0 and continue there. Also IMO PC++ shoul be replaced by a void function that check tha every increment does not go byond 0xFFFF but started over 0x0000 instead. On last thing I'm less sure is that cycles should be given by the giant switch according to instructions BACK to the Cpu.execute() so you shoud not have to think first how many cylcles you want to execute but rather read how many it took.
Michel GRIGAUT
Michel GRIGAUT 8 ай бұрын
Sorry for typos (I'm French)
enlightendbel
enlightendbel Жыл бұрын
To note, hardware mapped IO does not mean the memory isn't accessible. You can read/write to it without issue. It's just that the mapped device can also read/write to it directly. This can cause issues, but can also be used to pull some brilliant programming level compression. And dear lord did NES developers use every trick in the book to do so. Say, you have a texture that is pretty much the same binary code as a part of your actual program that actually does something important. Or you notice that part of your program looks pretty similar to one of your textures. Then why use a texture at all if you can just put that actual program code in the part of memory mapped for video and use the code itself as your texture?
Heinz Kessler
Heinz Kessler Жыл бұрын
I loved the 6502 and its instruction set. Wrote some code for the Apple ][ in assembly back then.
Steven Van Hulle
Steven Van Hulle Жыл бұрын
You should have seen the Motorola 6809. IMO the most beautiful 8-bit complex instruction set microprocessor ever. It had 59 base instructions, but with an advanced set of addressing modes gave a total of 1500-ish instructions. Two stack pointers (which could also be used as index registers), two index registers (which could also be used as stack pointers), two accumulators, relocatable and re-entrant code, you name it.
Cigmorfil
Cigmorfil 2 жыл бұрын
48:00 Using subroutine test address of $4242 could hide a bug of swapping the MSB and LSB.
Dave Poo
Dave Poo 2 жыл бұрын
Good point.
mykalimba
mykalimba 2 жыл бұрын
11:00 Doing anything other than setting PC to (0xfffc) dirties your CPU implementation, IMO. The only thing a real 6502 does on startup is set the PC to (0xfffc) and start running code. Everything else that happens (clearing/setting flags, setting stack pointer, etc.) is system-dependent and outside of the scope of the CPU itself.
Dave Poo
Dave Poo 2 жыл бұрын
Maybe, but zeroing the memory and the flags was a big help when testing without having a full system working or even implemented.
Mike
Mike Жыл бұрын
Use stdint.h. There is no guarantee that unsigned short is actually 16bits on all systems. With uint16_t you can be sure and the code becomes easier to understand.
27c3: Reverse Engineering the MOS 6502 CPU (en)
51:57
Christiaan008
Рет қаралды 345 М.
How to find broken links in Selenium ? | QA Automation Talk
12:11
QA Automation Talk
Рет қаралды 4
Cool hair prank 😂 #shorts
0:30
Mr DegrEE
Рет қаралды 62 МЛН
Cool hair prank 🔥 Watch Till end 😂 #shorts
0:29
Mr DegrEE
Рет қаралды 11 МЛН
My mom is Werewolf!😳😡☠️ #shorts
0:37
DaMus
Рет қаралды 6 МЛН
NES Emulator Part #1: Bitwise Basics & Overview
40:12
javidx9
Рет қаралды 406 М.
Emulating a 6502 system in JavaScript • Matt Godbolt • GOTO 2016
45:23
Cold Fusion is Back (there's just one problem)
19:53
Sabine Hossenfelder
Рет қаралды 911 М.
Stephen Edwards and Bill Mensch - The Genesis of the 6502 Microprocessor
43:12
Vintage Computer Federation
Рет қаралды 8 М.
Crucial SSD Drive Failed - Can we save data.?
23:40
NorthridgeFix
Рет қаралды 600 М.
How Nvidia Won AI
18:08
Asianometry
Рет қаралды 339 М.
Getting the Commander X16 running
42:46
Adrian's Digital Basement
Рет қаралды 157 М.
NES Architecture Explained
18:28
NesHacker
Рет қаралды 118 М.
Cool hair prank 😂 #shorts
0:30
Mr DegrEE
Рет қаралды 62 МЛН