In general, with microcontrollers, if you don't use interrupts, don't wait at the end of I/O for it to complete, wait at the start of the i/o for the previous operation to complete. That gives you time to fetch the next data item without losing precious time. Of course, with that hardware bug, it's pointless.
But does the bug affect the interrupt bit too? I think that's what Ben planned, but he always shows a simpler implementation first. The whole point of using a dedicated chip for serial I/O was so the CPU was free to do something else, and that doesn't happen right now (with or without the hardware bug.) So it's safe to assume the interrupt-based implementation will come at a later video.
@@dkosmari Might get away with it if only ever using half duplex and you can enable the interrupt for rx and tx separately. You might also be able to determine if it's an rx or tx interrupt as long as both flags were never going to get set simultaneously, but that is getting a bit dodgy (interrupt source is always serial, if not an rx interrupt must be a tx one).
@@threeMetreJim According to another comment, the interrupt bit is also buggy for transmissions. At this point, it might just be better sticking a USB controller in there instead.
@@dkosmari Were there any 8-bit home computers that actually used this chip? From what I remember, they all seemed to have a custom implementation for serial comms. Maybe this is why.
Your channel should be mandatory in all computer engineering curriculum. Hardware and Software design together is something every cs engineer should know about.
I half agree with this sentiment because I, as a student, feel like the professor should understand and teach the material. Not offload their work that I'm paying for onto a TH-cam channel that can be found for free.
@@bkucenski was it implementation as well. I think design part is done in all CS programs in the computer architecture course. But the real life hardware software integration is not done anywhere as far as i understand. You learn that from job. Or do simple microprocessor programming in micro c.
Man, I knew nothing of electronics and Ben's video series last year got me so intrigued, entertained and just overall curious. I did a deep dive, scouring the web for info and now I am retrofitting 555s everywhere I can, thinking that if Ben can make a computer based on those, he'd be proud to know he's inspired me into using archaic tech to build my own things. It's sooooo so fun to play with and to learn how to use to that level.
This kind of thing is so useful, to show how sometimes there are bugs that aren't your fault and aren't documented. Important for newcomers to know that this kind of thing happens
23:24 that reset button is asking for a hardware or software debouncing implementation, could be interesting to compare both approaches to deal with it.
@@snitkofb It's actually super easy. Just add a 100 ms delay loop at the very start of your program. That way, while the reset line is "bouncing" it will just be stuck resetting the initial startup delay.
I'd go with a hardware approach just in case there anything else that needs to be cleanly reset. The 65C51 has a hardware reset pin that could be tied to the same reset line as the 65C02. I like the DS1813 EconoReset IC because it will hold it's reset pin low until the system voltage has stabalised. And it can also debounce and hold it's reset low via a push button. The MAX6816 switch debouncer could also be used but it doesn't cater for system power on (and is bizarrely expensive).
I feel like it would make more sense to wait for "transmit buffer empty" before you send a character, not after. The whole point of a UART is that you don't have to spend CPU time to deal with I/O, but now you're using CPU time to wait on the UART to deal with the I/O. Edit: Aaaand, of course that's where the bug is.
Awesome videos Ben, I've been following the series from the beginning! Just a minor point in your code style that I've noticed : when using buffered transmission where you write to a transmit register as you have done, it is preferable to check buffer full *before writing a new character to it. That way you can't accidentally corrupt an ongoing transmission, and you don't have to wait for the transmit to finish before returning from the sub, so more code can be executed while the character is being transmitted. This theory works for polled or interrupt driven routines. Doesn't help with this hardware bug of course!
He probably planned incremental improvements to the code, to eventually get to that, and the hardware flaw changed his plans. When he gets to integrate the serial interrupts that shouldn't be needed anyway, the handler will always just send or receive one single byte, and not do anything until the next interrupt.
@@dkosmari Ben didn't just stumble across the bug while preparing this video. He knew all about it well in advance and designed the lesson around it. He's a brilliant teacher and he did his homework.
Having not watched the whole video yet, I remember way back in 1984 when I was working with this chip. I vaguely remember a certain register had to be read THREE TIMES on initialization to fix a hardware bug.
Wow - this really brought back memories from the mid 80's... One of my first assignments in my career was using a similar Intel UART, 8251 if I recall, which had a very similar bug in one version of the chip. I was given a development board to update code for a government project. My code worked well in the lab and we shipped the EPROM with my code to the customer. Weeks later, I was told that the upgrade failed. The boss was very angry "Didn't you test this before sending it to our customer?" Fortunately another engineer I worked with was familiar with the problem and realized that I developed code on a system with a newer UART, while the customer's system had the older buggy UART. I felt horrible. How could I have known that the customer had different hardware? Fortunately it was a simple fix - and a valuable lesson learned.
They literally turned the "it's a bug" into a "it's a feature" simply because it would not be cost effective to fix the bug. I imagine you planned to make a video dedicated to "how to write I/O routines." And you ended up teaching a far more valuable lesson to engineers: hardware bugs are way too common, and you can't just switch suppliers when you gotta finish the product within 6 months.
You have no idea how much I love UART and RS232. It's dirt simple. It's not locked down like USB. It's been around forever, and it JUST WORKS for what it's designed to do. You don't see many consumer applications use it much but we use it every day at work and it's VERY appreciable.
I absolutely hate RS232. Not because it's bad, per se (it's actually really well suited for is purpose), but because it's used incorrectly. RS232 was never intended to be used for anything but connecting telephony equipment (like a modem) to terminals (like a computer). It specifies voltages outside the range of TTL chips, but many RS232 products on the market only work with 5v. It doesn't take ground loops into consideration because that was never an issue for its intended use. Most of the signals are ignored in modern applications. To make things worse, figuring out exactly which settings are being used a particular device is using can be frustrating. Baud rate, byte size, parity, and stop bits have to match on both sides or you get garbage. That's not an issue when you're using it for what it was designed for (just flip the DIP switches on the modem to the settings you want and use the same settings on your computer/terminal/whatever), but when the device you're talking to is on the other side of the plant and is inside a metal box halfway up the side of a hot tank it's not horribly helpful. The only reason RS232 has reached the popularity that it has is that a) every computer had a port for that back in the day and b) no popular alternatives emerged. In the early industrial automation world where no two manufacturers used the same protocol, RS232 was the one thing they could agree on. RS422 and RS485 suffer from some of the same shortcomings, but at least they're general purpose busses. The biggest problem with those is underspecification - every manufacturer has a different pinout for RS485 ports. It's the twenty-first century. Why don't we have a well-specified serial bus with automatic negotiation? The mind boggles.
19:33 - I appreciate anyone willing to update their documentation. Thank-you so much for doing all this. As a self-taught programmer, it's been absolutely fascinating learning about the inner workings of a computer. You present it all very well. Much love from Zimbabwe.
If not for the hardware bug, ‘send_char’ would be better as wait-and-then-send rather than send-and-then-wait as it would make it more likely that the cpu was doing useful work while the data was sending, rather than guaranteeing that the cpu was wasting cycles every time the data was sending.
Seriously love your videos. I tinkered as a youth with chips and very old systems (early x86 and younger).. this takes me back and at the same time opens my eyes at what I was deduced from my experiences (no manuals, minimal equipment and no teachers). I wish these vids were available as a kid.
That workaround fails if hardware handshaking via CTS is used, which this chip can do in hardware, whereas the 8250/8251/16450 did not support until the 16550 was introduced. The 6551 was a great chip if it would have been less quirky, like each vendor having different requirements for the crystal with the oscillator not starting reliably if you did not follow closely.
12:28 if you wait for the tx data to be empty *before* you send the data, it'll never have to wait unless it needs to (this solution waits for every character whether you're transmitting another straight after or not). EDIT: guess it doesn't matter lmao
If you set the 65C51 up to output the clock on the RxC pin (Pin 5) you can pass that in to the VIA and pulse count and use the interrupt to trigger when enough clock ticks have passed on passing out the data. So you can still use interrupts just not a 65C51 one.
How fast can the 6522 count? Can it keep up with the fastest baud rate? One approach that would definitely work is to use a second 65C51 to monitor the outgoing Tx signal by feeding it into its Rx input, using its Rx interrupt to signal that the main ACIA had finished transmitting.
TIL that WDC on those chips is for Western Design Center, not Western Digital Corporation. I always thought the HDD company dabbled in some chips and supporting cards.
My approach would be: 1.) Before send, wait for bit 4 to be 1 2.) After send, check if bit 4 is zero 3.) If zero, return from subroutine 4.) If one, go to wait loop Why? With chip without bug, I'm not wasting time waiting. If I need to send another character, before previous one is sent, only then I need to wait. If bit 4 is one, that is clear sign that chip is bugged (with assumption that check is faster than sending byte) and fallback is activated.
It's a clever approach, but unfortunately now you've instead created a more subtle performance bug, where your processor's performance will be greatly different while transmitting serial data depending on what UART chip you happen to have.
@@pv2b that is issue only if application is timming critical (which should be avoided by using timers), otherwise it's maximizing performance depending on available HW. Of course, timmer could be used instead of loop delay, but since timmers are also limited resources, it often can be better used.
I think my approach would be to avoid using defective ICs. I mean it sounds like kind of a flippant reply but I actually went through the different options, patch the code, patch the hardware, etc. - just selecting for ICs that actually work properly seems like the option that makes the most sense. The thing about writing additional code to patch around the defective IC (and detect whether the IC is defective and switch behavior accordingly) is that it wastes code space. On a CPU with a 16-bit address space, that space is precious...
@@tetsujin_144 that approach is already covered in video and it has some bad news for you - only defective ICs are produced. You can hunt for old stock and used IC's but that is expensive sport with limited stocks...
12:10 the "send_char" routine should first do the "tx_wait" and then send the character. With a working chip (without the hardware bug) this will in principle reduce busy waiting (polling) the transmit data register empty bit, because the transmitting will happen in parallel with other code running. Edit: naturally at least four more earlier comments pointing out the same thing are present.
I substituted a 16550 UART. it isn't limited to 2 MHz like the 65c51 chip is. Running this one at 4 MHz. Also added decoding that puts everything in the 7000 area vs 5000. Recently added a FLASH IC that will be used as a file storage device. Had a successful test the other day. Still need] to make some kind of simple file system to store and keep track of files. @BenEater thank you so much for the enjoyment that myself and so many others have experienced with this series.
This content is exactly what I was looking for, I just do not want to become a software developer that learn framework X or y, I am really curious about how what computer Science is and this channel is amazing, keep going and thank you
I think using the 22s as both a timer and irq controller for the 51 makes this neater. One send buffer and one receive buffer. Timer to time output to 51, cascade the receive irq from the 51. Have a proper int handler that polls the 22 for which int, fills or empties the buffers, clears int state in the right place. Whatever function fills the buffer blocks on buffer full etc. Suppose there might be another workaround using the tx interrupt if that isn't buggy as well and you can actually tell the difference between the two.
In the initial implementation of send_char (which assumed there was no hardware fault), I think it would have been better to do the status check at the beginning rather than at the end. That way you wouldn't have to wait for characters to be sent unless you were trying to send characters too quickly.
This bug offers a perfect opportunity to introduce a timer and interrupt routine... You could even read status immediately after sending a byte to choose whether to use the timer or the status bit (maybe a pointer to the correct code and an indirect jump). At 12:50, to me it is more reasonable to pha and wait for transmit buffer empty before pla, sending the byte, and going on while the ACIA transmits the bits. If the calling code needs the sent byte, they can pha before the call and pla after the call.
> If the calling code needs the sent byte, they can pha before the call and pla after the call. This gets into the interesting matter of calling conventions and binary interfaces. Your statement is perfectly reasonable, that the caller of a send_serial_byte(char a) routine probably doesn't need to re-use the parameter after the call. However, if you have some subroutines use caller-saved registers and other subroutines use callee-saved registers, the programmer will forever need to keep that distinction in mind. Sticking to one convention or the other saves programmer effort, even if it loses a few cycles here and there.
Speaking of serial chip bugs, there is another prominent one. You remember PC serial cards with the 16550A serial chip? The 16550 was a revolutionary serial port- it had and has a 16 byte buffer so you don’t lose bytes if the CPU falls behind a bit. For example at 9600 baud the CPU has to grab a byte every 1000 microseconds or so, other wise a byte gets dropped. But a 16 byte buffer gives the CPU some breathing room. It also lowers the number of interrupts needed so less time is wasted handling interrupts. Except that the original 16550 had a bug! The 16 byte buffer didn’t work. It wasn’t until the A or maybe AF suffix version that the serial buffer worked.
I think it was only the 16550AFN that didn't have any bugs. The 8250, 16450 and early 16550's all had problems of some sort. I was a technician in the early 90's and used to swap the chips all the time since the 16450, 16550AF and 16550AFN were all interchangeable. That was back in the day when a lot of factories and companies were still using computers as terminals over rs232.
It might be more efficient to check the tx buffer status before transmitting rather than after since any work done between transmits will reduce busy waiting.
He might have planned that out, but since that tx status bit doesn't work at all, it's useless to optimize it. Regardless, the serial routines are still blocking, and he put a dedicated serial chip precisely to avoid blocking the CPU while doing I/O. To be actually more efficient, he will have to handle interrupts (if the interrupt bits are bug-free.)
@@dkosmari From the datasheet: '. The Transmitter Interrupt should never be enabled because the Transmitter Shift Register (TSR) is written when the TDR is written.' Basically you cannot do interrupt based transmitting with the W65C51 due to this bug, nor can you do polling based transmitting... The next most logical step (as Ben Eater has already covered this partially in earlier videos) would be to use the timer in the W65C22 to handle UART transmission timing using interrupts so that the UART transmit routine becomes non-blocking.
@@dkosmari It is severely flawed with this bug, unless you're only receiving data... Alternatives are the older R6551 chips for instance but those can be tricky to find depending on where you live etc. and are usually limited to 1-2Mhz. But even without the bug, the 6551 ACIA's are fairly limited anyway as they have no FIFO buffers, meaning you have to send data one Byte at a time. More 'advanced' UARTs have FIFO buffers, allowing you to throw 16 Bytes or more to the UART and have the UART just send all of them in order, as soon as the first Byte is sent you can throw in the next one in case you're sending more than 16 Bytes. But for most cases, 16 Bytes/characters is more than enough. Those UARTs also have 16 Byte FIFO buffers for the receiving data, meaning you won't easily miss data even if the CPU is very busy. But yeah, if you stick the W65C51 onto a board with firmware that uses the transmit bit/interrupts it will either corrupt transmitted data or hang the CPU in infinite interrupts unfortunately.
@@dkosmari Note that a primary objective was to avoid having the serial write block the read. If one of the VIA timers are free, it can be used to time the delay. The system initialization routine could start the timer so it will be expired on the first call, and the transmit routine first checks if there is a read byte, and if so stores it in a buffer location, then checks if the timer has expired, if not busy loops until it has, then writes the transmit byte, then starts the timer and returns.
I was pretty excited to get this hooked up. I found that when I ran this program, text printed to the LCD fine however the computer did not send back the proper character to the serial program. It would print the message fine and the character that I pressed showed up properly on the LCD. I am using the print_char routine from the keyboard interface program. TWO.....HOURS....LATER I realized that this routine destroys the top four bits of the A register before returning from the sub routine. I guess you can put the send_char routine before the print_char routine. You can also do a pha right after the pla on line 13 of the print_char routine. Im interested if anyone has a more efficient way to do this. Seems kind of redundant to pla and pha right after each other.
A very minor thing; in your code, you use the pair LDA $reg ; AND #$const just to set the zero flag. This is the sort of thing the BIT instruction is designed for - and as a nice side effect, it leaves A undamaged.
Wonder if some sort of resistor capacitor circuit, along with a comparator across the tx lines could be used to check whether a byte was sending, rather than the delay. I imagine you're setting up interrupts soon so the computer doesn't have to sit checking for data as before.
I built a 6502 system in 2017 and remember this very bug being the bane of my existence, before I checked the WDC datasheet (or maybe it was some forum or something). All the documentation I had about the chip was from other older manufacturers who didn't have that issue.
Wow! That Tx bug! That probably explains why a data transmission test I made around 1984 wouldn’t work! What a relief to finally know it wasn’t me being dumb!
in the send_char function I would wait for the status update *before* sending the data. this way you can continue doing other work without having to wait for the confirmation but if you try to send another byte it will wait until it is ready. right now, you're eagerly waiting for the status update when you could do something else during that wait time
You could work around the hardware bug with an external integrator circuit too. Charge an RC delay with the transmit line and when the charge dissipates, the line is clear. Couple that to the data line with some logic gates and you could replicate the correct behavior without looping.
21:32 Amazing to see one side note generate this error correction video. But having a time based delay mean one has to remark this in the src on how 1mhz affect this. The timing has to be in the src and not deleted 22:59 and not 23:06 which removes these. In fact those 2/3 cycles mentioned should be doc.
Hate this bug. I did bang my head against the wall for hours on this years ago and I am still salty. Blocking the CPU to send a block of data makes me sad. Using up a whole timer interrupt also makes me sad. And this chip is not cheap! >:[
Thanks, man this brings back memory. The old RUN magazine, I think it was, published free shareware or free software that you could enter using the machine language monitor in C=64, if you did not have. a copy of the monitor, you could enter that in basic that they published as well. I spent many an hour with the love of my life sitting next to me calling out the hex numbers as I typed them into the monitor and if all went well, I would have a new game to save to a floppy drive so I could. load it instead of having to enter it every time I wanted it. Eventually I tired of this and ordered the magazine with the floppy included so all I had to do was insert the disk and have fun. One of the games they had was my favorite, well I should say my wife's favorite. You see she was a math genius and love games where she could best me, especially on computers because I was a computer wiz and her math skills trumped most of the computer's back then. God I miss her, I lost her to cancer 3 years ago after 51 years and 4 days of pure love and joy.
Excellent video. I was going to ask if you sold kits. I’m glad you mentioned it. I’ve never used the 6502, so this might be fun to follow along. Reminds me of the old Heathkit days.
Great video. A little long for my taste since I mainly wanted to see the part near the end where you explain the workaround. Maybe switch 'debouncing' could be the topic for your next video! :P
When transmitting, why not wait for the buffer to be empty before writing instead of after, that way you don't waste time waiting if you're not transmitting something else right away.
I had the same thought - though since the "wait for the buffer to be empty" feature of the serial IC is defective, that strategy doesn't work. To do something with that CPU time other than just marking the passage of time, you'd need a timer or some other means of determining if the UART had enough time to send the data.
20:10 The way I understand that 2021 datasheet, that bit will be 0 during transmission but you will see it set to 1 once the reg is empty. So instead of the delay, I think you could also poll waiting for that bit to transition to high.
The issue is that the bit is 1 when it is ready to transmit and "not 0" when it is transmitting. It only is 0 when it isn't transmitting and isn't ready to transmit. This may happen if the hardware handshake lines are used, and the receiver signals it's not ready to receive, but I wouldn't bet on that either.
I have an old 6551 (date code 7933). All register bits and interrupts work as expected. (Looks like some old 6551's are still available at Jameco on clearance. It's not a WDC chip and I don't think it has the bug.) The problems with this old chip: it is not a "C" version and needs a quiet and steady 5V, and doesn't work much faster than 2Mhz - it requires wait states at higher CPU clocks. (I programmed an ATF22V10C to handle CPU clock stretching.) Things stopped working when I upped the processor clock to 8Mhz and switched to the WDC 65C51. The same exact code didn't work on the "new" 65C51 and it drove me NUTS before I realized it was their hardware bug. I eventually designed discreet hardware to divide the serial clock output and provide the interrupt at the correct time. Writing on one address started the counter, reading another address confirmed it was the interrupting device, and another stopped the counter and reset the interrupt. By the time I was done, I didn't have room for more '51s and didn't relish the idea of making another board with so many discreet chips (two 74HC373's, 74HC245, 74HC192, 74HC193, 74HC154, 74HC74, etc). My next attempt was simpler and used a timer on a 65C22 to do the interrupt timing. Everything worked fine until I zapped something a while ago and shelved the project in frustration. Maybe I'll rebuild and try again - more carefully this time. My goal is one serial port for debugging, one for a PuTTY terminal, and at least one for talking to an Arduino and/or Raspberry PI. And since another commenter mentioned I2C, I managed to score two PCF8584P's (20 pin dip version). They need wait states too (down to 1Mhz IIRC), but otherwise worked fine (until I zapped things.) As of today, Mouser has an SOIC version in stock - "PCF8584T/2,512". I believe there's a faster/better chip out there, but I don't recall the part number. Thanks for the vids by the way. You got me interested in microprocessor hardware again after 40 years of just programming.
I don't think it works with the 6502, but newer chips like the in Arduino you could set a timer to raise an interrupt flag to send the next byte, at which point of course you might as well implement your own buffer as well
Hi Ben, that was fun. I do recommend switching out that uart though. The 65xx support chips line was notorious for bugs. You would serve your followers better by switching to an 8250 or one of its derivatives. This design is still used today as the UART for many SOCs. Alternatively, the more powerful (but complex) Zilog 8530 is good. It was used in early Macintosh and Apple IIgs computers.
12:33 can't you check the buffer emtpy flag before sending the new byte? Wait for it to clear, send the byte and carry on doing other stuff. you might be able to send more bytes per second (with a chip without the bug, that is...)
I have a suggestion: Turn this computer into a calculator. Give it an equation over serial, get the result back. Good project to learn about floating-point (not necessarily IEEE-754, but still floating-point), circular buffers (to store the input as it comes in), Reverse Polish Notation, and the shunting-yard algorithm
Hey Ben, It is my understanding that both W65C51N and W65C51S are sold by WDC but have subtle differences. Notice that your datasheets have different part numbers. BTW, thanks you for your awesome videos!
I believe W65C51S isn't actually available for sale, at least not widely - only some engineering samples exist. The S version also doesn't appear to have the bug, so the first datasheet Ben showed is actually correct for that part. And it confuses me how WDC managed to fix the bug but more than 15 years later they still only produce the version with the bug.
@@TheGrejp They've only ever done one production run for the W65C51N. The chips available now were all produced a bit more than a decade ago. It's a really slow seller (watch how the stock numbers on Mouser change compared to, say, the W65C02S)
I think you are mistaken, perhaps. When you load it into the xmit reg, it is then placed into the shift reg to be shifted out immediately, if that shift reg is empty. This leaves the transmit reg empty. You could of course place another bytes into the xmit reg at this time, even before xmit is complete. Of course, it's been 40+ years ago and mine was a tad bit more complex (interrupts and other goodies), that is how I remember it. And, that was on original IBM PC.
I think it's smarter to wait for the outgoing register empty flag to be set before sending, not after sending. If you wait after, it is going to wait every time, even if you only send one byte. If you wait before, it might immediately be ready and it can send the data while the 6502 is doing other work instead of looping idle for the data to be sent. It is also safer, because some other factor might cause the chip not to be ready to send. Checking just before actually sending is safer than to wait for the chip to be ready again after sending and then returning some time later without re-checking. Or in other words It's a better idea to do "check for cliff, if no cliff then take step forward" instead of "take step forward, check for cliff"
The bug in the 6551 prevents you from being able to "Check for cliff". It always says "nah, there's no cliff". If you don't twiddle your thumbs a bit (either before OR after sending), you'll fly right into the void. If the Transmit Data Register Empty flag actually worked, you would be correct. Check before sending. Then when it says it's OK, give it the byte an go do something else.
Is there a reason you used hex values to initialise the ACIA instead of binary values as we have in the past? Just seems an extra step to have to convert to/from binary as you read the data sheet and need to revise/make changes etc.
Good video, I keep meaning to get back into some digital stuff. For some reason my electronics hobby keeps migrating back to tube gear. Wonder if assembly is like riding or falling off a bike. Enjoyed it
Ben seems to have added back 5 or 6 videos on connecting a keyboard. If you have not been watching from the get-go you would miss it. I presume they were left out later as it is not part of the end-product. Thanks for adding it back Ben. Any chance of getting a Kicad schematic for what I guess is the final product from a hardware point of view?
Would the fancy way to do this be to use the timer on the VIA chip to generate timed interrupts, and defer the waiting to an interrupt handler, which would transmit a character per invocation out of a buffer in memory, until it hit a null character, then switch off the timer? I was thinking of multiprocessing with the 6502 (and especially, the 65816), and it made me wonder, if you use hardware interrupts, which processor should the interrupt go to? That made me read up on programmable interrupt controllers. It occurred to me that it would be pretty easy to use one output from a VIA to control the routing of interrupts from any given device to either CPU A or B. Other applications of the VIA for bus control that I can think of are clock speed, and bank switching (for example, switching out slow ROM once the bootloader is loaded into underlying faster shadow RAM, then boosting the clock speed).
The VIA timer would probably be the best solution here, as long as it's not already occupied by another function of course. Multiprocessing the 6502 is fairly simple (CPU_0 has the bus on clock HIGH, CPU_1 on clock LOW), the 65816 with its multiplexed address/data bus can't share the bus that easily so you'd need some sort of bus arbitration logic. I'd just send all IRQs to one CPU, or spread them out logically. So if you've got CPU_0 handling UART data and CPU_1 handling some sort of VGA implementation have UART interrupts go to CPU_0 and VGA related interrupts (if any) go to CPU_1. I've also looked into multiprocessing the 6502, the 65816 being too much of a pain as you'd have to use double clock rates and at a 10Mhz clock, getting SRAM + chip select logic within 25ns becomes very difficult. Besides that, the main issue was figuring out what the second CPU would even be doing...
If I've read your question correctly. The W65C51 already has an IRQB pin. It can be programmed by addressing the COMMAND register to go low on both transmit and receive (if necessary). If you get an interrupt from the ACIA WITHOUT the Receiver Data Register Full then you can assume it is time to transmit another byte. That works around the STATUS register bug. If you're going multiprocessor then it's easiest to have only one MPU connected (by address bus decoding) to the ACIA and that processor should receive the interrupt. It becomes the UART serving processor (along with whatever else it does).
@@YateyTileEditor Except for the bug in the WDC part, where the transmit interrupt should never be enabled. Then the transmit routine putting the data into a circular buffer and an interrupt routine driven by a VIA countdown timer taking data out of the circular buffer would indeed be the most effective solution.
@@YateyTileEditor perfect answer, many thanks. And I appreciate the added guidance on one MPU being the UART server. I had already sort of got there because using a dedicated VIA for bus and bank control seems obvious, plus bit banging SPI to a boot SD card,, and the primary MPU could service those VIA applications too. So I think I'm aiming at an asymmetrical dual MPU arrangement, where the primary does all the housekeeping and basic IO, and the secondary is free as possible to just calculate (and communicate with its own VIAs).
@@brucemcfarling6594 I think the bug is only in the output of the transmit buffer full bit on the status register. Internally the '51 knows the state correctly and can bring IRQB low when it is time to transmit the next byte. The W65C51 is a drop in replacement for the 6551 as long as software does does not use bit 4 of the status register. Interrupts are fine and are the suggested work around in the WDC datasheet. (Although they're not really a work around, interrupts are more the expected usage of any 6551). [EDIT] The 2014 WDC datasheet I'm using has a Note: "It is recommended to using IRQ handling " from the W65C51N. The later datasheets do not. The W65C51S does not have the bug but is not generally available.
@@dkosmari Do you know when the last batch was made? When it runs out, they'll either make another batch or the chip will become extinct. If they want to make another batch they can either use the old masks or fix the problem. It's all a question of money. My feeling is that they sell so few of the chips, partly because of its age but mostly because of the bugs, that they'll just let it die - there are plenty of alternative UART chips available, though none so easy to use with the 6502. Thankfully the W65C02 itself works exactly as documented and quite a bit better than intended (there are users able to clock them at 20MHz and beyond) so it's comparatively cheap to keep it in production.
It's unlikely they'd fix it. It's well known, apparently didn't affect many people (since they took so long to update the documentation), and probably there isn't much demand for this chip today. It would cost too much to change the design and retool the machinery, and you risk introducing another bug, or breaking someone's design that (maybe accidentally) relies on the bug.
Seems like the delay loop only really needs to be used when just sending a string and not when echoing the input back. If doing the delay always I'd be concerned that you might wait too long between reads of the receive register
Thanks, very nice video. It so happens I'm busy writing an OS for the C128, and also want to support the 6551 (for turbo232, swiftlink), and this info is very helpfull for that. I wonder if this means the transmit interupt is not working either? Do you happen to know? I only have access to an original (1980s era) 6551, so can't test this. Looks like it is not difficult to detect if a specific 6551 has this bug or not by sending a byte and seeing if the transmit buffer empty bit changes to 0 inmediately after having written the byte to the data register.
CAN would be an excellent choice too, the built-in priority arbitration in the ID header to prevent message collisions on a shared bus is pretty cool to see in action.
@@alanangelfire1217 Bleh I had to grapple with CAN in a recent project and I found the reflections / terminating resistors / checking impedence a real pain, had to get the oscilliscope out to find out the right frequency and troubleshoot reflections, CAN ground which isn't regular ground, it has optional heartbeats and different modes of operating.. bleah.. Of course now I have more understanding it wouldn't have been so bad, but the experience didn't make me a fan
I'd love to see an overlay of some kind to show the vim key sequences you're using - like removing everything to the right of your cursor. My vim-foo is weak...
would be funny to fix the hardwarebug with hardware. every time a byte is written to the chip a 555 can fire and set a bit high for the delaytime. Then the CPU can do other stuff in that time instead of waiting.. Also in send_char - wouldn't it be better to check first if the buffer is full and then send the char? In this case it makes no difference because the chip is only used by the CPU, but it would be the right thing to do.
Or when there is a write to Data register location, pull the Data Terminal Ready line to "not ready" state for a long enough period of time to send the data, and use the hardware flow control status.instead.
The data sheet for the W65C51S (2007) does *not* describe the issue; the data sheet for the W65C51N (2021) *does* describe the issue. Is it possible that chips with an S are fine and chips with an N are buggy? Or do all the chips have the same markings and the same issues, and it's only the documentation that got updated?
Many moons ago I was working on a project that needed a watchdog timer, basically a timer that was reset on every loop, with the loop being shorter than the watchdog timer. When we tried it, the watchdog kept triggering. It took a while to find the problem. We were using a CMOS version of the timer, which operated differently to the TTL version in that the TTL version did reset on every pulse whilst the CMOS version ignored any pulse whilst it was active and only restarted after it had finished counting. Thus the TTL version was useful as a watchdog but the CMOS version wasn’t. The person who designed the circuit assumed the CMOS version was exactly compatible with the TTL version. Are there other devices where this sort of problem arises? Is there a problem with CMOS that exacerbates these issues?
In general, with microcontrollers, if you don't use interrupts, don't wait at the end of I/O for it to complete, wait at the start of the i/o for the previous operation to complete. That gives you time to fetch the next data item without losing precious time.
Of course, with that hardware bug, it's pointless.
But does the bug affect the interrupt bit too? I think that's what Ben planned, but he always shows a simpler implementation first. The whole point of using a dedicated chip for serial I/O was so the CPU was free to do something else, and that doesn't happen right now (with or without the hardware bug.) So it's safe to assume the interrupt-based implementation will come at a later video.
@@dkosmari Might get away with it if only ever using half duplex and you can enable the interrupt for rx and tx separately. You might also be able to determine if it's an rx or tx interrupt as long as both flags were never going to get set simultaneously, but that is getting a bit dodgy (interrupt source is always serial, if not an rx interrupt must be a tx one).
@@threeMetreJim According to another comment, the interrupt bit is also buggy for transmissions. At this point, it might just be better sticking a USB controller in there instead.
I really don't understand why he wouldn't use interrupts. It's infinitely better than polling. And more interesting too.
@@dkosmari Were there any 8-bit home computers that actually used this chip? From what I remember, they all seemed to have a custom implementation for serial comms. Maybe this is why.
Your channel should be mandatory in all computer engineering curriculum. Hardware and Software design together is something every cs engineer should know about.
Computer engineering*
I half agree with this sentiment because I, as a student, feel like the professor should understand and teach the material. Not offload their work that I'm paying for onto a TH-cam channel that can be found for free.
My computer science program included designing a four bit alu starting with logic gates. This stuff is taught at least at some colleges.
@@bkucenski was it implementation as well. I think design part is done in all CS programs in the computer architecture course. But the real life hardware software integration is not done anywhere as far as i understand. You learn that from job. Or do simple microprocessor programming in micro c.
Man, I knew nothing of electronics and Ben's video series last year got me so intrigued, entertained and just overall curious. I did a deep dive, scouring the web for info and now I am retrofitting 555s everywhere I can, thinking that if Ben can make a computer based on those, he'd be proud to know he's inspired me into using archaic tech to build my own things. It's sooooo so fun to play with and to learn how to use to that level.
This kind of thing is so useful, to show how sometimes there are bugs that aren't your fault and aren't documented. Important for newcomers to know that this kind of thing happens
Unexpected Robert Miles sighting! Love your work
Ive wondered how many exploits were implimented on purpose for a manufacturer backdoor.
Trap for young players. I recently found a bug in the Toshiba chip LED driver.
Took me years to accept that sometimes I just need to debug the software/hardware im using instead of blaming myself
"what's the difference between science and messing around?"
"writing it down"
23:24 that reset button is asking for a hardware or software debouncing implementation, could be interesting to compare both approaches to deal with it.
Oddly enough I remember Eater talking about debouncing before. That must have been for the 8-bit breadboard computer then.
@@PhoenixClank yep - whilst building the clock module due to needing to debounce the single step button
Hard to debounce a reset in software…
@@snitkofb It's actually super easy. Just add a 100 ms delay loop at the very start of your program. That way, while the reset line is "bouncing" it will just be stuck resetting the initial startup delay.
I'd go with a hardware approach just in case there anything else that needs to be cleanly reset. The 65C51 has a hardware reset pin that could be tied to the same reset line as the 65C02.
I like the DS1813 EconoReset IC because it will hold it's reset pin low until the system voltage has stabalised. And it can also debounce and hold it's reset low via a push button. The MAX6816 switch debouncer could also be used but it doesn't cater for system power on (and is bizarrely expensive).
I feel like it would make more sense to wait for "transmit buffer empty" before you send a character, not after. The whole point of a UART is that you don't have to spend CPU time to deal with I/O, but now you're using CPU time to wait on the UART to deal with the I/O.
Edit: Aaaand, of course that's where the bug is.
Its funny how ben's video seem leading and like they're gonna run into an issue, when that was planned all along to teach a lesson
Awesome videos Ben, I've been following the series from the beginning!
Just a minor point in your code style that I've noticed : when using buffered transmission where you write to a transmit register as you have done, it is preferable to check buffer full *before writing a new character to it. That way you can't accidentally corrupt an ongoing transmission, and you don't have to wait for the transmit to finish before returning from the sub, so more code can be executed while the character is being transmitted. This theory works for polled or interrupt driven routines.
Doesn't help with this hardware bug of course!
He probably planned incremental improvements to the code, to eventually get to that, and the hardware flaw changed his plans. When he gets to integrate the serial interrupts that shouldn't be needed anyway, the handler will always just send or receive one single byte, and not do anything until the next interrupt.
@@dkosmari Ben didn't just stumble across the bug while preparing this video. He knew all about it well in advance and designed the lesson around it. He's a brilliant teacher and he did his homework.
Having not watched the whole video yet, I remember way back in 1984 when I was working with this chip. I vaguely remember a certain register had to be read THREE TIMES on initialization to fix a hardware bug.
The fixed delay subroutine now locks you in to using 19200 baud. At other speeds, a different loop (or rather, value of reg. x) is needed.
Wow - this really brought back memories from the mid 80's... One of my first assignments in my career was using a similar Intel UART, 8251 if I recall, which had a very similar bug in one version of the chip. I was given a development board to update code for a government project. My code worked well in the lab and we shipped the EPROM with my code to the customer. Weeks later, I was told that the upgrade failed. The boss was very angry "Didn't you test this before sending it to our customer?" Fortunately another engineer I worked with was familiar with the problem and realized that I developed code on a system with a newer UART, while the customer's system had the older buggy UART. I felt horrible. How could I have known that the customer had different hardware? Fortunately it was a simple fix - and a valuable lesson learned.
They literally turned the "it's a bug" into a "it's a feature" simply because it would not be cost effective to fix the bug.
I imagine you planned to make a video dedicated to "how to write I/O routines." And you ended up teaching a far more valuable lesson to engineers: hardware bugs are way too common, and you can't just switch suppliers when you gotta finish the product within 6 months.
This is a very well-known bug. He definitely knew about it before making the video. But yes, a valuable lesson.
@@pikadroo I don't think you are.
Imagine if they renamed it to "Chip ready", now that is how you turn it into a feature!
@@pikadroo Who said that?
@@pikadroo it is because of your comments attacking the creator of this video series?
You have no idea how much I love UART and RS232. It's dirt simple. It's not locked down like USB. It's been around forever, and it JUST WORKS for what it's designed to do. You don't see many consumer applications use it much but we use it every day at work and it's VERY appreciable.
yea 232 and 485 own industry
all the simple comms you need over 2-3 wires
I absolutely hate RS232. Not because it's bad, per se (it's actually really well suited for is purpose), but because it's used incorrectly.
RS232 was never intended to be used for anything but connecting telephony equipment (like a modem) to terminals (like a computer). It specifies voltages outside the range of TTL chips, but many RS232 products on the market only work with 5v. It doesn't take ground loops into consideration because that was never an issue for its intended use. Most of the signals are ignored in modern applications.
To make things worse, figuring out exactly which settings are being used a particular device is using can be frustrating. Baud rate, byte size, parity, and stop bits have to match on both sides or you get garbage. That's not an issue when you're using it for what it was designed for (just flip the DIP switches on the modem to the settings you want and use the same settings on your computer/terminal/whatever), but when the device you're talking to is on the other side of the plant and is inside a metal box halfway up the side of a hot tank it's not horribly helpful.
The only reason RS232 has reached the popularity that it has is that a) every computer had a port for that back in the day and b) no popular alternatives emerged. In the early industrial automation world where no two manufacturers used the same protocol, RS232 was the one thing they could agree on.
RS422 and RS485 suffer from some of the same shortcomings, but at least they're general purpose busses. The biggest problem with those is underspecification - every manufacturer has a different pinout for RS485 ports.
It's the twenty-first century. Why don't we have a well-specified serial bus with automatic negotiation? The mind boggles.
19:33 - I appreciate anyone willing to update their documentation. Thank-you so much for doing all this. As a self-taught programmer, it's been absolutely fascinating learning about the inner workings of a computer. You present it all very well. Much love from Zimbabwe.
Yeah they updated it to say it's broken.
If not for the hardware bug, ‘send_char’ would be better as wait-and-then-send rather than send-and-then-wait as it would make it more likely that the cpu was doing useful work while the data was sending, rather than guaranteeing that the cpu was wasting cycles every time the data was sending.
Yeah... very annoying bug
They clearly know about the bug, so why don't they make a rev.2 with a bit option for backwards-compability or something?!
Seriously love your videos. I tinkered as a youth with chips and very old systems (early x86 and younger).. this takes me back and at the same time opens my eyes at what I was deduced from my experiences (no manuals, minimal equipment and no teachers).
I wish these vids were available as a kid.
Good to see your video again
Thanks, Ben. Another terrific instalment in an excellent series. There's nothing else like this on the internet!
Next video you can make a bootloader to load binary from serial so that you don't have to keep flashing the EEPROM
Mans making assembly looking like python.
You are on another level Ben 👏🏾👏🏾
That workaround fails if hardware handshaking via CTS is used, which this chip can do in hardware, whereas the 8250/8251/16450 did not support until the 16550 was introduced. The 6551 was a great chip if it would have been less quirky, like each vendor having different requirements for the crystal with the oscillator not starting reliably if you did not follow closely.
12:28 if you wait for the tx data to be empty *before* you send the data, it'll never have to wait unless it needs to (this solution waits for every character whether you're transmitting another straight after or not).
EDIT: guess it doesn't matter lmao
I'd hope that's what he'd have done next if the bug hadn't existed lol
If you set the 65C51 up to output the clock on the RxC pin (Pin 5) you can pass that in to the VIA and pulse count and use the interrupt to trigger when enough clock ticks have passed on passing out the data. So you can still use interrupts just not a 65C51 one.
This is exactly what I did and it works!
That’s actually a really good idea, I think I may use this.
How fast can the 6522 count? Can it keep up with the fastest baud rate? One approach that would definitely work is to use a second 65C51 to monitor the outgoing Tx signal by feeding it into its Rx input, using its Rx interrupt to signal that the main ACIA had finished transmitting.
Glad you posted this or everyone with the UART kit would be tearing their hair out. Very informative video, thanks
TIL that WDC on those chips is for Western Design Center, not Western Digital Corporation. I always thought the HDD company dabbled in some chips and supporting cards.
Western Digital _did_ make chips and cards, just not these ones.
You know it's gonna be a good Saturday when Ben releases a new video!
Sadly he disappeared for like the whole pandemic, right when we all needed him the most ; ;
@@mashrien at least he's back, releasing his good content once again
Even though I've been writing embedded software for 45 years, I still love watching you write software on the 6502 like I did in 1979.
@23:10 you should have put the delay before the status register loop to avoid additional delays on chips that work properly.
My approach would be:
1.) Before send, wait for bit 4 to be 1
2.) After send, check if bit 4 is zero
3.) If zero, return from subroutine
4.) If one, go to wait loop
Why? With chip without bug, I'm not wasting time waiting. If I need to send another character, before previous one is sent, only then I need to wait. If bit 4 is one, that is clear sign that chip is bugged (with assumption that check is faster than sending byte) and fallback is activated.
It's a clever approach, but unfortunately now you've instead created a more subtle performance bug, where your processor's performance will be greatly different while transmitting serial data depending on what UART chip you happen to have.
@@pv2b that is issue only if application is timming critical (which should be avoided by using timers), otherwise it's maximizing performance depending on available HW. Of course, timmer could be used instead of loop delay, but since timmers are also limited resources, it often can be better used.
I think my approach would be to avoid using defective ICs. I mean it sounds like kind of a flippant reply but I actually went through the different options, patch the code, patch the hardware, etc. - just selecting for ICs that actually work properly seems like the option that makes the most sense.
The thing about writing additional code to patch around the defective IC (and detect whether the IC is defective and switch behavior accordingly) is that it wastes code space. On a CPU with a 16-bit address space, that space is precious...
@@tetsujin_144 that approach is already covered in video and it has some bad news for you - only defective ICs are produced. You can hunt for old stock and used IC's but that is expensive sport with limited stocks...
i just love your videos will buy my own kit with a friend soon
I had been wondering if you were going to talk about the bug. I'm pleased to see you did!
12:10 the "send_char" routine should first do the "tx_wait" and then send the character. With a working chip (without the hardware bug) this will in principle reduce busy waiting (polling) the transmit data register empty bit, because the transmitting will happen in parallel with other code running.
Edit: naturally at least four more earlier comments pointing out the same thing are present.
I substituted a 16550 UART. it isn't limited to 2 MHz like the 65c51 chip is. Running this one at 4 MHz. Also added decoding that puts everything in the 7000 area vs 5000. Recently added a FLASH IC that will be used as a file storage device. Had a successful test the other day. Still need] to make some kind of simple file system to store and keep track of files. @BenEater thank you so much for the enjoyment that myself and so many others have experienced with this series.
This is pure gold. New favorite TH-cam channel.
This content is exactly what I was looking for, I just do not want to become a software developer that learn framework X or y, I am really curious about how what computer Science is and this channel is amazing, keep going and thank you
I think using the 22s as both a timer and irq controller for the 51 makes this neater. One send buffer and one receive buffer. Timer to time output to 51, cascade the receive irq from the 51. Have a proper int handler that polls the 22 for which int, fills or empties the buffers, clears int state in the right place. Whatever function fills the buffer blocks on buffer full etc.
Suppose there might be another workaround using the tx interrupt if that isn't buggy as well and you can actually tell the difference between the two.
"You know, you could really end up banging your head against the wall." Meaning you banged your head against the wall for us.
In the initial implementation of send_char (which assumed there was no hardware fault), I think it would have been better to do the status check at the beginning rather than at the end. That way you wouldn't have to wait for characters to be sent unless you were trying to send characters too quickly.
This bug offers a perfect opportunity to introduce a timer and interrupt routine... You could even read status immediately after sending a byte to choose whether to use the timer or the status bit (maybe a pointer to the correct code and an indirect jump).
At 12:50, to me it is more reasonable to pha and wait for transmit buffer empty before pla, sending the byte, and going on while the ACIA transmits the bits. If the calling code needs the sent byte, they can pha before the call and pla after the call.
> If the calling code needs the sent byte, they can pha before the call and pla after the call.
This gets into the interesting matter of calling conventions and binary interfaces. Your statement is perfectly reasonable, that the caller of a send_serial_byte(char a) routine probably doesn't need to re-use the parameter after the call. However, if you have some subroutines use caller-saved registers and other subroutines use callee-saved registers, the programmer will forever need to keep that distinction in mind.
Sticking to one convention or the other saves programmer effort, even if it loses a few cycles here and there.
Speaking of serial chip bugs, there is another prominent one. You remember PC serial cards with the 16550A serial chip? The 16550 was a revolutionary serial port- it had and has a 16 byte buffer so you don’t lose bytes if the CPU falls behind a bit. For example at 9600 baud the CPU has to grab a byte every 1000 microseconds or so, other wise a byte gets dropped. But a 16 byte buffer gives the CPU some breathing room. It also lowers the number of interrupts needed so less time is wasted handling interrupts. Except that the original 16550 had a bug! The 16 byte buffer didn’t work. It wasn’t until the A or maybe AF suffix version that the serial buffer worked.
I think it was only the 16550AFN that didn't have any bugs. The 8250, 16450 and early 16550's all had problems of some sort. I was a technician in the early 90's and used to swap the chips all the time since the 16450, 16550AF and 16550AFN were all interchangeable. That was back in the day when a lot of factories and companies were still using computers as terminals over rs232.
It might be more efficient to check the tx buffer status before transmitting rather than after since any work done between transmits will reduce busy waiting.
He might have planned that out, but since that tx status bit doesn't work at all, it's useless to optimize it. Regardless, the serial routines are still blocking, and he put a dedicated serial chip precisely to avoid blocking the CPU while doing I/O. To be actually more efficient, he will have to handle interrupts (if the interrupt bits are bug-free.)
@@dkosmari From the datasheet: '. The Transmitter Interrupt should never be enabled because the Transmitter Shift Register (TSR) is written when the TDR is written.' Basically you cannot do interrupt based transmitting with the W65C51 due to this bug, nor can you do polling based transmitting...
The next most logical step (as Ben Eater has already covered this partially in earlier videos) would be to use the timer in the W65C22 to handle UART transmission timing using interrupts so that the UART transmit routine becomes non-blocking.
@@someguy4915 So the chip is pretty much useless? Surely this chip would not work on any board that expects a functioning one?
@@dkosmari It is severely flawed with this bug, unless you're only receiving data...
Alternatives are the older R6551 chips for instance but those can be tricky to find depending on where you live etc. and are usually limited to 1-2Mhz.
But even without the bug, the 6551 ACIA's are fairly limited anyway as they have no FIFO buffers, meaning you have to send data one Byte at a time. More 'advanced' UARTs have FIFO buffers, allowing you to throw 16 Bytes or more to the UART and have the UART just send all of them in order, as soon as the first Byte is sent you can throw in the next one in case you're sending more than 16 Bytes.
But for most cases, 16 Bytes/characters is more than enough.
Those UARTs also have 16 Byte FIFO buffers for the receiving data, meaning you won't easily miss data even if the CPU is very busy.
But yeah, if you stick the W65C51 onto a board with firmware that uses the transmit bit/interrupts it will either corrupt transmitted data or hang the CPU in infinite interrupts unfortunately.
@@dkosmari Note that a primary objective was to avoid having the serial write block the read. If one of the VIA timers are free, it can be used to time the delay. The system initialization routine could start the timer so it will be expired on the first call, and the transmit routine first checks if there is a read byte, and if so stores it in a buffer location, then checks if the timer has expired, if not busy loops until it has, then writes the transmit byte, then starts the timer and returns.
I was pretty excited to get this hooked up. I found that when I ran this program, text printed to the LCD fine however the computer did not send back the proper character to the serial program. It would print the message fine and the character that I pressed showed up properly on the LCD. I am using the print_char routine from the keyboard interface program. TWO.....HOURS....LATER I realized that this routine destroys the top four bits of the A register before returning from the sub routine. I guess you can put the send_char routine before the print_char routine. You can also do a pha right after the pla on line 13 of the print_char routine. Im interested if anyone has a more efficient way to do this. Seems kind of redundant to pla and pha right after each other.
Thank you :D Saved me from bad weekend
At some point I'm going to have to rewatch this whole series! Nicely done.
A very minor thing; in your code, you use the pair LDA $reg ; AND #$const just to set the zero flag. This is the sort of thing the BIT instruction is designed for - and as a nice side effect, it leaves A undamaged.
ah yes, the infamous mantra "we'll just fix it in software"
And "it's not a bug, it's a feature."
This is colossal work to be programming your own computer! it's really inspiring! beautiful work !
Wonder if some sort of resistor capacitor circuit, along with a comparator across the tx lines could be used to check whether a byte was sending, rather than the delay. I imagine you're setting up interrupts soon so the computer doesn't have to sit checking for data as before.
I built a 6502 system in 2017 and remember this very bug being the bane of my existence, before I checked the WDC datasheet (or maybe it was some forum or something). All the documentation I had about the chip was from other older manufacturers who didn't have that issue.
Wow! That Tx bug! That probably explains why a data transmission test I made around 1984 wouldn’t work! What a relief to finally know it wasn’t me being dumb!
in the send_char function I would wait for the status update *before* sending the data. this way you can continue doing other work without having to wait for the confirmation but if you try to send another byte it will wait until it is ready. right now, you're eagerly waiting for the status update when you could do something else during that wait time
You could work around the hardware bug with an external integrator circuit too. Charge an RC delay with the transmit line and when the charge dissipates, the line is clear. Couple that to the data line with some logic gates and you could replicate the correct behavior without looping.
21:32 Amazing to see one side note generate this error correction video. But having a time based delay mean one has to remark this in the src on how 1mhz affect this. The timing has to be in the src and not deleted 22:59 and not 23:06 which removes these. In fact those 2/3 cycles mentioned should be doc.
I love this channel! Thanks Ben:)
Would be interesting to see what needs to be done to make the code work well with both versions of the chip. Anyway, very interesting stuff!
Hate this bug. I did bang my head against the wall for hours on this years ago and I am still salty. Blocking the CPU to send a block of data makes me sad. Using up a whole timer interrupt also makes me sad. And this chip is not cheap! >:[
Thanks, man this brings back memory. The old RUN magazine, I think it was, published free shareware or free software that you could enter using the machine language monitor in C=64, if you did not have. a copy of the monitor, you could enter that in basic that they published as well. I spent many an hour with the love of my life sitting next to me calling out the hex numbers as I typed them into the monitor and if all went well, I would have a new game to save to a floppy drive so I could. load it instead of having to enter it every time I wanted it. Eventually I tired of this and ordered the magazine with the floppy included so all I had to do was insert the disk and have fun. One of the games they had was my favorite, well I should say my wife's favorite. You see she was a math genius and love games where she could best me, especially on computers because I was a computer wiz and her math skills trumped most of the computer's back then. God I miss her, I lost her to cancer 3 years ago after 51 years and 4 days of pure love and joy.
Helpful video
Way above my level of comprehension but still great viewing!
Thank you! Will there be a video about sound chips like YM?
Excellent video. I was going to ask if you sold kits. I’m glad you mentioned it. I’ve never used the 6502, so this might be fun to follow along. Reminds me of the old Heathkit days.
Great video. A little long for my taste since I mainly wanted to see the part near the end where you explain the workaround.
Maybe switch 'debouncing' could be the topic for your next video! :P
When transmitting, why not wait for the buffer to be empty before writing instead of after, that way you don't waste time waiting if you're not transmitting something else right away.
I had the same thought - though since the "wait for the buffer to be empty" feature of the serial IC is defective, that strategy doesn't work. To do something with that CPU time other than just marking the passage of time, you'd need a timer or some other means of determining if the UART had enough time to send the data.
@@tetsujin_144 he did implement a wait loop, still after writing though.
20:10 The way I understand that 2021 datasheet, that bit will be 0 during transmission but you will see it set to 1 once the reg is empty. So instead of the delay, I think you could also poll waiting for that bit to transition to high.
That's how it's supposed to work, but not how it actually works.
The issue is that the bit is 1 when it is ready to transmit and "not 0" when it is transmitting. It only is 0 when it isn't transmitting and isn't ready to transmit. This may happen if the hardware handshake lines are used, and the receiver signals it's not ready to receive, but I wouldn't bet on that either.
I have an old 6551 (date code 7933). All register bits and interrupts work as expected. (Looks like some old 6551's are still available at Jameco on clearance. It's not a WDC chip and I don't think it has the bug.) The problems with this old chip: it is not a "C" version and needs a quiet and steady 5V, and doesn't work much faster than 2Mhz - it requires wait states at higher CPU clocks. (I programmed an ATF22V10C to handle CPU clock stretching.) Things stopped working when I upped the processor clock to 8Mhz and switched to the WDC 65C51. The same exact code didn't work on the "new" 65C51 and it drove me NUTS before I realized it was their hardware bug. I eventually designed discreet hardware to divide the serial clock output and provide the interrupt at the correct time. Writing on one address started the counter, reading another address confirmed it was the interrupting device, and another stopped the counter and reset the interrupt. By the time I was done, I didn't have room for more '51s and didn't relish the idea of making another board with so many discreet chips (two 74HC373's, 74HC245, 74HC192, 74HC193, 74HC154, 74HC74, etc). My next attempt was simpler and used a timer on a 65C22 to do the interrupt timing. Everything worked fine until I zapped something a while ago and shelved the project in frustration. Maybe I'll rebuild and try again - more carefully this time. My goal is one serial port for debugging, one for a PuTTY terminal, and at least one for talking to an Arduino and/or Raspberry PI. And since another commenter mentioned I2C, I managed to score two PCF8584P's (20 pin dip version). They need wait states too (down to 1Mhz IIRC), but otherwise worked fine (until I zapped things.) As of today, Mouser has an SOIC version in stock - "PCF8584T/2,512". I believe there's a faster/better chip out there, but I don't recall the part number. Thanks for the vids by the way. You got me interested in microprocessor hardware again after 40 years of just programming.
I don't think it works with the 6502, but newer chips like the in Arduino you could set a timer to raise an interrupt flag to send the next byte, at which point of course you might as well implement your own buffer as well
Hi Ben, that was fun. I do recommend switching out that uart though. The 65xx support chips line was notorious for bugs.
You would serve your followers better by switching to an 8250 or one of its derivatives. This design is still used today as the UART for many SOCs.
Alternatively, the more powerful (but complex) Zilog 8530 is good. It was used in early Macintosh and Apple IIgs computers.
Awesome video, thank you!
12:33 can't you check the buffer emtpy flag before sending the new byte? Wait for it to clear, send the byte and carry on doing other stuff. you might be able to send more bytes per second (with a chip without the bug, that is...)
Ah, 6502 assembly code!
This brings me back to the early 80's when I bought my Acorn Atom with its built in assembler/disassembler.
.
I have a suggestion: Turn this computer into a calculator. Give it an equation over serial, get the result back. Good project to learn about floating-point (not necessarily IEEE-754, but still floating-point), circular buffers (to store the input as it comes in), Reverse Polish Notation, and the shunting-yard algorithm
Debounce that button!!
Great video as always, Ben.
Hey Ben, It is my understanding that both W65C51N and W65C51S are sold by WDC but have subtle differences. Notice that your datasheets have different part numbers. BTW, thanks you for your awesome videos!
I believe W65C51S isn't actually available for sale, at least not widely - only some engineering samples exist. The S version also doesn't appear to have the bug, so the first datasheet Ben showed is actually correct for that part. And it confuses me how WDC managed to fix the bug but more than 15 years later they still only produce the version with the bug.
@@TheGrejp They've only ever done one production run for the W65C51N. The chips available now were all produced a bit more than a decade ago.
It's a really slow seller (watch how the stock numbers on Mouser change compared to, say, the W65C02S)
In case anyone else tries using one of the older S6551 chips, you'll also want to make sure you ground pin 9 which is the CTS (clear to send) pin.
This also goes for the unused input pins for channel 2 of the MAX232. Leaving them floating could interfere with channel 1 according to the datasheet.
This also goes for the unused input pins for channel 2 of the MAX232. Leaving them floating could interfere with channel 1 according to the datasheet.
I think you are mistaken, perhaps. When you load it into the xmit reg, it is then placed into the shift reg to be shifted out immediately, if that shift reg is empty. This leaves the transmit reg empty. You could of course place another bytes into the xmit reg at this time, even before xmit is complete. Of course, it's been 40+ years ago and mine was a tad bit more complex (interrupts and other goodies), that is how I remember it. And, that was on original IBM PC.
Very good to not bang heads off walls.
Think you need a debounce circuit on that button :)
I think it's smarter to wait for the outgoing register empty flag to be set before sending, not after sending. If you wait after, it is going to wait every time, even if you only send one byte. If you wait before, it might immediately be ready and it can send the data while the 6502 is doing other work instead of looping idle for the data to be sent. It is also safer, because some other factor might cause the chip not to be ready to send. Checking just before actually sending is safer than to wait for the chip to be ready again after sending and then returning some time later without re-checking.
Or in other words It's a better idea to do "check for cliff, if no cliff then take step forward" instead of "take step forward, check for cliff"
The bug in the 6551 prevents you from being able to "Check for cliff". It always says "nah, there's no cliff". If you don't twiddle your thumbs a bit (either before OR after sending), you'll fly right into the void. If the Transmit Data Register Empty flag actually worked, you would be correct. Check before sending. Then when it says it's OK, give it the byte an go do something else.
Is there a reason you used hex values to initialise the ACIA instead of binary values as we have in the past?
Just seems an extra step to have to convert to/from binary as you read the data sheet and need to revise/make changes etc.
Good video, I keep meaning to get back into some digital stuff. For some reason my electronics hobby keeps migrating back to tube gear. Wonder if assembly is like riding or falling off a bike. Enjoyed it
Ben seems to have added back 5 or 6 videos on connecting a keyboard. If you have not been watching from the get-go you would miss it. I presume they were left out later as it is not part of the end-product. Thanks for adding it back Ben. Any chance of getting a Kicad schematic for what I guess is the final product from a hardware point of view?
What an epic fail! I wonder what process was used to transfer the original working design to its CMOS variant?
Would the fancy way to do this be to use the timer on the VIA chip to generate timed interrupts, and defer the waiting to an interrupt handler, which would transmit a character per invocation out of a buffer in memory, until it hit a null character, then switch off the timer?
I was thinking of multiprocessing with the 6502 (and especially, the 65816), and it made me wonder, if you use hardware interrupts, which processor should the interrupt go to? That made me read up on programmable interrupt controllers. It occurred to me that it would be pretty easy to use one output from a VIA to control the routing of interrupts from any given device to either CPU A or B. Other applications of the VIA for bus control that I can think of are clock speed, and bank switching (for example, switching out slow ROM once the bootloader is loaded into underlying faster shadow RAM, then boosting the clock speed).
The VIA timer would probably be the best solution here, as long as it's not already occupied by another function of course.
Multiprocessing the 6502 is fairly simple (CPU_0 has the bus on clock HIGH, CPU_1 on clock LOW), the 65816 with its multiplexed address/data bus can't share the bus that easily so you'd need some sort of bus arbitration logic.
I'd just send all IRQs to one CPU, or spread them out logically. So if you've got CPU_0 handling UART data and CPU_1 handling some sort of VGA implementation have UART interrupts go to CPU_0 and VGA related interrupts (if any) go to CPU_1.
I've also looked into multiprocessing the 6502, the 65816 being too much of a pain as you'd have to use double clock rates and at a 10Mhz clock, getting SRAM + chip select logic within 25ns becomes very difficult. Besides that, the main issue was figuring out what the second CPU would even be doing...
If I've read your question correctly. The W65C51 already has an IRQB pin. It can be programmed by addressing the COMMAND register to go low on both transmit and receive (if necessary). If you get an interrupt from the ACIA WITHOUT the Receiver Data Register Full then you can assume it is time to transmit another byte. That works around the STATUS register bug.
If you're going multiprocessor then it's easiest to have only one MPU connected (by address bus decoding) to the ACIA and that processor should receive the interrupt. It becomes the UART serving processor (along with whatever else it does).
@@YateyTileEditor Except for the bug in the WDC part, where the transmit interrupt should never be enabled. Then the transmit routine putting the data into a circular buffer and an interrupt routine driven by a VIA countdown timer taking data out of the circular buffer would indeed be the most effective solution.
@@YateyTileEditor perfect answer, many thanks. And I appreciate the added guidance on one MPU being the UART server. I had already sort of got there because using a dedicated VIA for bus and bank control seems obvious, plus bit banging SPI to a boot SD card,, and the primary MPU could service those VIA applications too.
So I think I'm aiming at an asymmetrical dual MPU arrangement, where the primary does all the housekeeping and basic IO, and the secondary is free as possible to just calculate (and communicate with its own VIAs).
@@brucemcfarling6594 I think the bug is only in the output of the transmit buffer full bit on the status register. Internally the '51 knows the state correctly and can bring IRQB low when it is time to transmit the next byte.
The W65C51 is a drop in replacement for the 6551 as long as software does does not use bit 4 of the status register. Interrupts are fine and are the suggested work around in the WDC datasheet. (Although they're not really a work around, interrupts are more the expected usage of any 6551).
[EDIT] The 2014 WDC datasheet I'm using has a Note: "It is recommended to using IRQ handling " from the W65C51N. The later datasheets do not. The W65C51S does not have the bug but is not generally available.
wonder if theres a way to get WDC to fix that bug
Of couse they can, but that would mean cooking another batch of chips.
@@dkosmari Do you know when the last batch was made? When it runs out, they'll either make another batch or the chip will become extinct. If they want to make another batch they can either use the old masks or fix the problem. It's all a question of money. My feeling is that they sell so few of the chips, partly because of its age but mostly because of the bugs, that they'll just let it die - there are plenty of alternative UART chips available, though none so easy to use with the 6502. Thankfully the W65C02 itself works exactly as documented and quite a bit better than intended (there are users able to clock them at 20MHz and beyond) so it's comparatively cheap to keep it in production.
It's unlikely they'd fix it. It's well known, apparently didn't affect many people (since they took so long to update the documentation), and probably there isn't much demand for this chip today. It would cost too much to change the design and retool the machinery, and you risk introducing another bug, or breaking someone's design that (maybe accidentally) relies on the bug.
Excellent video as usual thank you. Still hoping for a SerDes series to jailbreak PCIe to connect to breadboard 😁
you virtually sawed off the legs of my chair when you counted how many clock cycles costed each code line in the last loop delay 😂👍
Danke für Deine Videos 😀😀😀😀😀
Seems like the delay loop only really needs to be used when just sending a string and not when echoing the input back. If doing the delay always I'd be concerned that you might wait too long between reads of the receive register
23:02 remember when you could count clock cycles ! because it wasn't a pipelines out-of-order CPU
Me not understanding a word of what is being said but just wanting background noise to not feel lonely
You chose well.
Exelente amigo!!! Gracias😊
Thanks, very nice video.
It so happens I'm busy writing an OS for the C128, and also want to support the 6551 (for turbo232, swiftlink), and this info is very helpfull for that.
I wonder if this means the transmit interupt is not working either? Do you happen to know? I only have access to an original (1980s era) 6551, so can't test this.
Looks like it is not difficult to detect if a specific 6551 has this bug or not by sending a byte and seeing if the transmit buffer empty bit changes to 0 inmediately after having written the byte to the data register.
Love your videos
yuh new video
Thanks for this one :)
This seems like a cool video.
Still think a video on the I2C protocol would be cool too
Absolutely, such an amazing protocol for low-level peripherals.
CAN would be an excellent choice too, the built-in priority arbitration in the ID header to prevent message collisions on a shared bus is pretty cool to see in action.
SPI and I2S too 🙂
@@mariorobben794 He made one on SPI already
@@alanangelfire1217 Bleh I had to grapple with CAN in a recent project and I found the reflections / terminating resistors / checking impedence a real pain, had to get the oscilliscope out to find out the right frequency and troubleshoot reflections, CAN ground which isn't regular ground, it has optional heartbeats and different modes of operating.. bleah.. Of course now I have more understanding it wouldn't have been so bad, but the experience didn't make me a fan
I'd love to see an overlay of some kind to show the vim key sequences you're using - like removing everything to the right of your cursor. My vim-foo is weak...
That would be D (or, if you don't like the shorthand, d$ will also do). vim comes with a helpful tutorial (vimtutor from shell, or :Tutor in nvim).
would be funny to fix the hardwarebug with hardware.
every time a byte is written to the chip a 555 can fire and set a bit high for the delaytime. Then the CPU can do other stuff in that time instead of waiting..
Also in send_char - wouldn't it be better to check first if the buffer is full and then send the char? In this case it makes no difference because the chip is only used by the CPU, but it would be the right thing to do.
Or when there is a write to Data register location, pull the Data Terminal Ready line to "not ready" state for a long enough period of time to send the data, and use the hardware flow control status.instead.
Beautiful work as always! Is there a practicable way to demonstrate or infer the bug directly without the updated data sheet?
Thanks
Hopefully you will also extend this excellent series with a sound and video chip (like Yamaha YM2149F and TMS9918A).
The data sheet for the W65C51S (2007) does *not* describe the issue; the data sheet for the W65C51N (2021) *does* describe the issue. Is it possible that chips with an S are fine and chips with an N are buggy? Or do all the chips have the same markings and the same issues, and it's only the documentation that got updated?
Sir can you make a video where we can build our own 8085 microprocessor training kit using 8085 microprocessor chip?
Many moons ago I was working on a project that needed a watchdog timer, basically a timer that was reset on every loop, with the loop being shorter than the watchdog timer. When we tried it, the watchdog kept triggering. It took a while to find the problem. We were using a CMOS version of the timer, which operated differently to the TTL version in that the TTL version did reset on every pulse whilst the CMOS version ignored any pulse whilst it was active and only restarted after it had finished counting. Thus the TTL version was useful as a watchdog but the CMOS version wasn’t. The person who designed the circuit assumed the CMOS version was exactly compatible with the TTL version. Are there other devices where this sort of problem arises? Is there a problem with CMOS that exacerbates these issues?