Wednesday, 12 July 2023

6502 : BEN2 : VIA : We need more LEDs - use a shift register

Requirement

Most recent BEN2 activity has been focussed on creating a user-friendly development environment.
We used a couple of VIA pins for our ROM-writing program and that only leaves three VIA pins available for LEDs, buttons and other things.

Adding an extra VIA would require me to sort out addressing logic (chips and wiring) to reserve a new address range for the second VIA so it would create major upheaval.  A less extreme alternative would be to change my LCD logic from the 8 to the 4 pin version.  However that would only give me four extra lines.

I prefer an option described by Garth Wilson which utilises the shift register function built into the 6522 VIA.



Shift Registers

Shift registers are commonly used for serial to parallel (SIPO) and parallel to serial (PISO) conversion.
The diagram above shows 74HC595 shift registers being used for SIPO.  A stream of 8 bits is sent from the VIA chip to a 595.  The 595 then latches the 8 bits to its output lines which can have LEDs or other output devices attached.  We only need three VIA pins to send the data.  Even better we can daisy chain the 595s so that we have 8,16,24, 32,....outputs.

Shift register operation is quite straightforward, I learned a bit more from a random article for the Onion SBC which provided the schematic below.  Individual bits (on pin 14) are transmitted into the register, at each clock pulse ( on pin 11).  At an appropriate time they are latched (using pin 12)  to a "storage register" so that the output can be used.  For our purposes the VIA will organise a stream of 8 bits on the CB2 pin with a clock signal on CB1.  When the 8 bits are in the 595 SR we will set CA2 high to latch these to Storage Register Outputs. 

This seems a fine way to proceed so that we can add some LEDs, or other outputs and  I purchased some 74HC595 chips from ebay for £0.60 each.

I dont foresee much need for more inputs but I could use 74HC165 shift registers provide PISO conversion.  One application area which could utilise this solution would be dip switches, each of which requires a line.

Connections

A good tutorial from DroneBotWorkshop provides pinouts for 74HC595
I connected this to an LED array on a breadboard.  There are 8 LEDs in the array and each is connected to ground using a resister in the resistor network.  This is very quick and easy to setup.  8 output pins from the 595 connect to the positive side of the LEDs so that when a 595 line goes high the associated LED lights up.  The 6522 VIA provides "bits" using CB2 and CB1 and they are output to the LED array when CA2 goes high.

Programming


With the wiring in place, we need a BEN2 program which will cause the W65C22 VIA to send appropriate signals to the '595.  Garth Wilson provides some guidance on his web page.  We need to use three W65C22 registers
        Shift Register    (SR)
        Auxiliary Control Register (ACR)
        Peripheral Control register (PCR)

Firstly, using PCR we set CA2 low, so there is no output from the '595.

Next we configure the 6522 to use the 6502 system clock (PHI2) to pulse serial bits to '595 as shown in the 6522 datasheet timing manual


We are ready to go.  Each time an 8-bit value is loaded to the W65C22 shift register
it is sent to '595 and as soon as we pulse CA2 high the data is output to LEDs.
The code is shown below, there is an init subroutine and a display subroutine.  I put a short delay in the display subroutine so that 6502 waits for bits to be transferred before they are displayed.

Result

This was an enjoyable and rewarding days work.  I have expanded the output capabilities of the system, adding 8 LEDs with very little effort.  Even better I could daisy chain a number of 74HC595 chips to add as many as I want.

Tuesday, 4 July 2023

6502 : BEN2 : Monitor v3 finally released

Monitor v2

A couple of months ago, at the end of April, I attached an ACIA on a breadboard to BEN2 which gave me terminal access to the system.  Using the serial connection I was able to download programs from my PC to BEN2 using a simplified xmodem transfer.  In early May I setup monitor v2 which included xmodem file transfer and other basic functions such as memory display, jump subroutine and clear screen.  

For the reset of May I focussed on creating AMI, a daughter board for BEN2 "peripherals" comprising the ACIA, LCD, buttons, LEDs and clock.  By the end of this work I had a robust, soldered daughter board alongside BEN2, mounted on a perspex stand.  To make the system neat, pretty and more reliable I detached the Arduino Mega which I had previously been using to download programs.  This gave me a hardware setup which begins to look like a real computer.

Using xmodem, meant that I could download and run programs more quickly and easily to RAM.  I spent some time making the process more streamlined, quicker, easier, more predictable and making programs relocatable so that I could have multiple concurrent programs in RAM.

The downside of using xmodem and not having the Arduino attached was that I couldn't update ROM.  Luckily monitor v2 has proved very reliable and I have been using it without problems for the last couple of months.

I was reluctant to reconnect the Arduino Mega again to update ROM programs and my thoughts turned to how I could run a 6502 program on BEN2 to update its ROM.

Writing ROM

Ben Eaters 6502 design required the ROM to be removed for programming. BEN2 was setup specifically so that the ROM could be updated in situ using an Arduino Mega.  There are only a couple of extra hardware connections required to achieve this; specifically it was necessary to control ROM /WE and ROM /OE, the ROM write-enable and output enable pins from the Arduino.

After a little experimentation I developed a program write-romx, which runs in RAM and copies a program from RAM into ROM.  I set up write-romx so that I could download it from the PC with the program destined for ROM attached to the end of it.

I wrongly thought that I had nearly finished my work at this stage.  I had to change my terminal emulator to resolve an issue and tweak the timings to ensure bigger downloads were reliable.  I also found an irritating error, not yet resolved, which stopped me using various ROM load addresses.

In addition to writing programs to ROM, I need to be able to write individual bytes, for example reset vectors, and I developed a script, using write-romx, to do this easily.

Libraries

Once I had a basic method for writing to ROM, I started to work on a new monitor program.  Initially I could only write short programs and I realised I could use monitor-v2 subroutine in my new programs providing I knew their ROM addresses.  I developed a little shell script which analyses an assembly map and extracts address information to create a symbol table.  I arranged for the symbol table to be in the form of a header file, with all subroutine names prefixed with "LIB".  I could now use the monitor-v2 subroutines in any of my programs just by prefixing them with "LIB".  This kept my program code and download times much shorter.

My previous attempts at providing libraries were very frustrating as any errors in version or address of subroutines usually caused programs to fail and the update process was manual.

Programs

Now that I had a platform to develop / update programs for monitor-v3 I could put some effort into providing some extra functionality.  I decided I should focus providing a set of "driver" subroutines for VIA hardware and the terminal.

At present I am using the LCD screen and LEDs on VIA I/O ports.

There are quite a few capabilities within the HD44780 driver chip such as scrolling text, custom characters.  I decided I only want basic ones to control the LCD screen.  I made sure that I can clear the screen, display character output starting at any location on the screen, delete top or bottom row.  I wrote a test program VIAtest to test new and updated subroutines.

If I needed an existing subroutine within VIAtest I could use a monitor-2 LIB subroutine.
Some subroutines needed to be updated before use and I used a local version within the program, with the corrections / additions.

I only needed LED subroutines to turn the red, amber, green LED on or off.  I did provide a blink function but it is limited to a fixed duration as I don't have a "key pressed" interrupt or button capability to terminate blinking.

I developed TERMtest to provide extra terminal functions.  Various useful functions were added to:

  • write out a large block of text
  • position cursor at particular [row, column] position
  • change the colour for character display
  • erase screen and display menu



Building monitor-3

Monitor-3 started life as a minimal cutdown version of monitor-2 that I could fit into 512 bytes.  The minimal functions provided were m (memory dump), p (clear some memory), s (subroutine jump), x (xmodem download), z (return to monitor at next level).  It used monitor-2 library routines as far as possible.

There was a lot of work build monitor-3 ready for burning to ROM.

Firstly I took all monitor-2 subroutines (except any local ones) and copied them to three files depening on function TERM (terminal control), VIA (LED, LCD) and SHARE (utilities).  I added new subroutines from VIAtest and TERMtest into these files and replaced old versions with any that had been updated.  I also updated my symbol and zero page variable definition header file memory_map_v1 with extra symbols and zero page variable names for new and updated values from monitor-3, VIAtest and TERMtest.

Once I had tested monitor-3, VIAtest and TERMtest to make sure they still worked I sanitised the programs and include files to make sure they confirmed to some basic standards

  • Tab character at start of line before each opcode
  • Comments aligned consistently
  • Registers saved / restored in all subroutines - or reason for not doing so provided
  • "cheap" labels with local scope, beginning with "@", used in all subroutines

Thirdly I had to remove all "LIB" references in monitor-3, VIAtest, TERMtest to make sure that I have a consistent set of subroutines in monitor-3 and no dependencies on monitor-2 (which will be deleted soon).  This gave me three working programs using a set of tested subroutines, all of which were local to the program or in shared include files.  The programs were about 1kB-1.5KB in size. 

Finally I was ready to write the new monitor so I connected up my two ROM write wires and ran the write-romx program to save monitor-3 to $A001.
I saved a NOP code at $A000 (to circumvent my unresolved "lost byte" error) and changed the reset vector using a script to $A000.
I hit the reset button and saw...the monitor-3 menu πŸ˜€πŸ˜€πŸ˜€πŸ˜€



I now have an updated monitor in ROM which forms a great basis for further development.

Concluding notes

The ability to develop programs easily in RAM and flash new monitor versions to ROM are great improvements.  My previous boards have all gone downhill as I added functions so it was important to me that I have a good environment to develop further ideas. 
Previously, the provision of the AMI daughter board was a big step forward making the hardware reliable.  Monitor-3 should provide a stable development environment, including the use of library subroutines and represents a similarly important step in making the software reliable.

In summary, this is wonderful because BEN2 is starting to feel like a (1980s) computer.

Sunday, 2 July 2023

6502 : BEN2 : Flashing an updated Monitor to ROM

Background

Back in mid-May I disconnected the Ardino Mega from BEN2 and subsequently I didn't have any process to update BEN2 ROM.  This meant that I was totally dependent on the existing ROM monitor program  at address $8000 (called xmodem-2-shell-1 or mon2 for short) for loading any code I wanted to test or run.  

In mid-June I successfully implemented a program (write-romx) which enables me to write code into ROM.  This gave me the potential capability to upgrade the ROM monitor code. (to lib-3-mon or mon3 for short).

Copy mon2

I made a copy of the mon2 source program to form the basis for the new monitor.  The aim was to make sure it worked in RAM then load it into ROM.  Unfortunately, for reasons I didn't understand at the time, the copy didn't work.  It appeared to download correctly but then wouldnt run.

As is usual in these circumstances, my approach is to cut down the program until I have at least a small part that is working.  Eventually I cut the monitor down so that it just displayed a menu.  The code was only about 300 bytes.

Create a symbol table

I started to embark on work to add functionality / subroutines gradually.

However I had a little flash of inspiration.  I realised that all the subroutines I needed were already part of mon2 and resided in ROM.  For example, the subroutine to display a menu is called cmdline_menu and looking at the link-map for mon2 I can see that it can be found in ROM starting at address $815A.
So all I need to do is replace my subroutine names with suitable ROM addresses and I can implement functionality with almost no extra code.

In fact, what I doing here is using mon2 code as a library.  This is similar to what was done on old home computers.  The vendors would, if you were lucky, would give you a list of ROM addresses for various functions you might want to use and you could then use them in your programs.

When I implemented libraries for my previous 6502 boards I ran into big problems with change control.  It was very easy to not update addresses when a routine moved to a different location and took much work to maintain a (somewhat flaky) list.  For this system to suceed I needed a better approach.

What I need is a symbol table showing subroutine names and their associated ROM addresses.  As I will only update ROM occasionally, when a new version of the monitor is released, this table wont change frequently.  The first change will occur when I cutover to a new monitor version.

My programming environment is now on WSL and I can easily access the ld65 link map which is created during the assembly - link procedure.
Using nifty linux tools like sed, awk, cut and column it didn't take long to process the link map, extracting all lines containing labels and their associated addresses.  Some of these addresses are for branch statements but it doesn't matter as long as I have all the subroutine names.  I could remove them manually, but so far I haven't needed to.

Once I have the labels I can format them and create a header file which I can use in my assembly programs.  In the snippet below you can see that I have added a prefix "LIB" to the subroutine names.

For example within mon2 in ROM the subroutine which displays a newline on the terminal is term_newline and it resides in the monitor at location $852B


If I want to use this ROM routine in my program I simply include the header file symbol_table.h and use "jsr LIBterm_newline".
If I want to I can incorporate the code within my program by copying the source in and issuing "jsr term_newline".
Generally, I want to use "LIB" subroutines unless I new an updated version of a routine, either for extra functionality or error correction.

Create a small mon3

Rather than add all the functions I implement into the monitor I aim to keep it compact.
Initially I have restricted myself to m (memory dump), s(call subroutine), x(xmodem download), p(clear memory range) and I have added an extra function z which I use when testing a new monitor version, it exits the RAM test monitor and returns control to the ROM version.
Other options which I dont think are vital were removed: c (clear screen), j(jump to address),l(sticky key), o(blink LED), r(reset)

All the functions I am using (m,s,x,p) were already in mon2 so it is easy to add them to mon3, mainly a question of using the LIB version of subroutines.  The assembly download procedure for mon3 is setup to download it to $3000.  Typically the programs I download will reside at $1000 and the write-romx program will work at $2000 so I aim to avoid mixups over memory.


The addition of a z option is very important as it allows exit from the test monitor to the ROM monitor.  The terminal prompt was changed from '>'  in mon2 to '$' in mon3 so you can always tell which version you are using.

Also I removed the stack initialisation code from mon3 for a couple of reasons.
Firstly, although stack initialisation allows us to check how the stack has built up and what it contains more easily, I never use this functionality.  Secondly if I initialise the stack within mon3 I cant issue a RTS opcode to return to mon2 as I have overwritten the stack entry containing a return address.  I dont forsee a need to put stack initialisation back.

Update ROM monitor

Once I was happy that the monitor was working in RAM I wanted to check that I could save it in ROM and use it to start up my system.
I connected the ROM Output Enable and Write Enable wires to the VIA chip to facilitate update of ROM contents and ran my write-romx script to assemble and download mon3 to RAM and ran write-romx to copy mon3 to ROM at $9000.
The results were underwhelming.  The program did not run properly at $9000 and I wasted some time mindlessly tinkering with scripts and download program changes.

I knew that mon3 was about 400 bytes long and worked when loaded at $3000 in RAM.  I looked at memory locations to compare this with the version loaded at $9000 in ROM.  I saw that the new code was 12 bytes longer!
For writing to ROM we download write-romx (about 120 bytes) to $2000 and mon3 code follows on immediately after write-romx in RAM.  I checked this code and found that it was also 12 bytes longer than the original, indicating that 12 bytes had been added during download.

As my download package (write-romx + mon3) was just over $200 bytes I guessed, correctly, that the error was caused by my program not working with bigger downloads.
I managed to cut out some more bytes from the mon3 executable to make the total download package less than $200 bytes.
When I tried the download + copy-to-ROM again, I found that the executable was the correct size and tmon3 ran correctly in ROM.


This is great, we now have a new working monitor in ROMπŸ˜€πŸ˜€πŸ˜€
The process of putting mon3 in ROM has a number of very rough edges but this represents a good step forward.

Another glitch I found, which  can be seen on the screenshot above is that the monitor menu isn't properly displayed.  I discovered this was due to a $00 byte erroneously inserted in ROM at address $9100.  It is another error to be resolved before the monitor is finally implemented.

Although we are not ready to cutover to the new monitor I did a quick test cutover.
I removed the ROM chip from BEN2 and inserted it in the EEPROM programmer.
I then changed the reset vector in $FFFC and $FFFD, using the xgpro software, from $8000 to $9000.
I reinserted the chip in BEN2, powered up and reset the computer and my new monitor appearedπŸ˜€
I then reset the ROM since mon3 is not yet ready for "production" use.

Fix programs larger than 512 bytes

I needed to work out why programs longer than 512 bytes ($200) would not load properly.

I wrote a test program to display text on the screen on the basis that it is easier to make a program bigger by adding text than it is by writing code.  Also printing out a block of text will reveal missing / incorrect characters easily.
My previous text display subroutine could only show 255 bytes so I amended it to be able print larger blocks of text.
Rather than inserting newline codes in my text I amended the subroutine so that a tilde character '~' in the text indicates a newline.  The display subroutine replaces it with a newline ($0A, $0D).


I found that as soon as my test program executable was more than 512 bytes long, these extra bytes were added and caused the program to mis behave.

I noticed that the extra characters were printable, and in fact they read "Minicom2.7.1".  Clearly my terminal emulation program, which is conveniently left running during a download, inserts these characters.  I realised that this is the Answerback message that minicom must insert when it receives an ENQ ($05) character.  I looked at the hex values for the program I was downloading but there was no $05 value there.  With a small flash of inspiration I realised that my xmodem download sends data in 128 byte blocks the download goes wrong near the start of the fifth block after 512 bytes have been sent.  The xmodem protocol specifies that a block number, in this case $05, is inserted in the header for each block, thus causing the error.

I looked to see if I could surpress the answerback in minicom but I could see no way.  I considered reverting to my previous download method, using putty where I pause a session, complete a download and restart the session; I considered this to be a pain, I have got used to the easier method minicom provides.
Next I tried out PICOcom, which promises to be a basic emulator, based on minicom, but with fewer features.  It works a treat, there is no answerback and my downloads are a lot more reliableπŸ˜€πŸ˜€
I was very pleased to be able to fix the problem without programming work.

Change individual ROM bytes

My write-romx program works by being downloaded to RAM with a header and payload immediately following it.  It copies the payload to ROM as specified in the header.
It occured to me that the payload could be one or two bytes long and I could use a script to put the bytes into the payload.

The format required for write-romx is:

<write-romx load address> <write-romx binary> <payload load address> <payload length> <payload>

The example below shows a two byte payload (the value “$9000”) loaded to address $FFFC, $FFFD


Thus I now have an easy way of changing the reset vector, without having to put the ROM chip in the EEPROM programmer.

Although I now have a good method for updating the ROM monitor and changing the reset vector to use a new monitor, there is always a possiblity for me to screw up.  If I change the reset vector incorrectly or the xmodem download subroutine isn't working my system is totally broken.

To safeguard my environment I made a ROM backup to another ROM chip using my EEPROM writer and xgpro software.

An irritating glitch

I mentioned earlier in this post that a single byte at address $9100 was missing from my copy. Obviously this would be disastrous if it happened again.  I did some in depth investigation to clarify what was happening.
The error doesn't occur when copying to ROM, only to RAM.
The existing byte at the problem location remains unchanged (i.e. the copy program isn't inserting a duff value).
There is a single byte which isn't copied even if the program is 1k in size.
The error doesn't occur at all load addresses:
$9000, $9100, $9200, $A000, $A200, $A100, $9040, $9080 all have an error after 256 ($100) bytes
$9001, $9203, $A001, $9010, $9020 dont have an error.

Sadly I will need to use a workaround:
When I update my monitor program I will use a load address of $9001 or $A001.
I can then change the reset vector to $9001 or $A001 to use the new monitor by default.

Concluding Notes

In my last blog post I described the method to load a program into ROM.  At the time I thought that I would soon be able to complete the monitor upgrade.  A few problems have set me back so this post has had to concentrate on sorting them out.  I am now confident that I am ready to update the monitor content and my next post will finish with monitor version 3 in use on BEN2.