Sunday, 11 June 2023

6502 : BEN2 : Xmodem Enhancement 2 : Flash programs to ROM

 Intro

Using the serial port for xmodem downloads has made the process of putting executable code into RAM a lot easier and we now have a very friendly environment for developing programs.

Previously I used an Arduino Mega to download programs to ROM for testing.  As serial downloads are more convenient I have removed the Arduino Mega from my system.  The Mega used to interfere with 6502 initialisation previously so the system is now more reliable.  In addition it was messy to have another board attached together with its PC USB connection.  However it does mean that I have no method for updating the ROM in situ.

Design

It occured to me that if I can write ROM from the Mega and write RAM from BEN2, I should be able to flash ROM from BEN2.  Looking at the Arduino sketch I was using, all I need to do in preparation was set ROM /OE pin high to disable ROM output and set ROM /WE pin low to enable writing.  I should then be able to write whatever I wanted to ROM before resetting the values afterwards to /WE=HIGH and /OE=LOW.  In fact my circuit used a pull-up resister on /WE and a pull-down resister on /OE so that by default, if the pins were not set the ROM would be in read-only mode.

BEN2's VIA chip provides GPIO capability so I can potentially connect two wires from my VIA chip to ROM/OE and ROM/WE and set them high or low as required.  Once that was done I could write data to ROM in the normal way using 6502 STA instruction.

There is one wrinkle I have to remember.  When ROM/OE is set low to disable output and allow new values to be input it is not possible to read machine code instructions from the ROM.  Consequently the subroutine which copies an executable to ROM must reside in RAM.

Proof of concept

I wrote a simple assembly program which stores a few characters (in fact the string "JOHN") to memory.  At first I set the target address as RAM and checked the data was stored properly using the monitor m-command to display a range of memory.
I have five VIA pins not used by the LCD PA0, PA1 (buttons) and PA2, PA3, PA4 (LEDs).  I attached wires from PA3 to ROM/WE and PA4 to ROM/OE so that when I set the values high or low I could see on the LEDs their status.  In my program I set PA3 low and PA4 high then wrote a string to an address ($9000) in ROM, then reset the pins to enable output and prevent writing.
The program worked fine, I was confident I could write data to ROM.

Attach a program

For the next few stages I practiced copying to RAM.  When this worked I would modify the program (slightly) to write to ROM.
My next program write-ramx used a loop to copy a sequence of bytes to RAM.  The target_address, data_length and data were specified in the program.  The Y register was incremented each time around the loop to copy to successive memory addresses.


I intentionally placed this data at the end of the program.  I then removed the data so that the last statement in the program was the label data.  Now I could take the executable write-ramx.x and append the program I wanted to copy at the end.  When I downloaded and executed write-ramx it copied the attached program to the target address.

In fact I also needed to encode the load address for write-ramx, the target_address and data_length in the download binary.  A complete binary takes the form:
   <\x00\x20><xmodem-3-write-ramx.x=70 bytes><\x00\x10><\x00\x16><via-3-LED-1x.x=22bytes>
In this example the load address for write-ramx is $2000, the target address for LED-1x is $1000 and the length of LED-1x is $0016.  I used a shell script to encode the addresses and length and to concatenate write-ramx with the payload program LEX-1x.  This package was then transmitted through the serial port to BEN2.

My simple program using the Y register as an index was limited to a maximum "8-bit" program size of 255 bytes.  For a full-sized program I went to 6502.org and found a "16-bit" copy subroutine by Bruce Clark called MOVEUP I could use. To use the subroutine I simply needed to specify a source address (the end of the program), a destination address (target_address) and size (data_length).
These were loaded directly from the executable into the program

Once this program was working I could arrange for BEN2 to go in ROM writing mode before the copy and reset afterwards.
The final program is shown below


Conclusion


My program write-romx now works.  In its final form it looks quite simple.  You set up the necessary addresses, enable writing to ROM, copy the data (i.e. payload program) and reset ROM to read-only.  The program is downloaded to RAM with a payload each time I need to put something into ROM.

Now I dont need to attach the Arduino Mega to write to ROM we are in a much better position.

In practice when developing programs iteratively I will usually load and execute them in RAM.  Once they are working I can use this method to copy them to ROM.

I have glossed over a few details here, partly because they aren't very interesting and partly because it is easy to become confused with the details.





Wednesday, 7 June 2023

6502 : BEN2 : Streamline downloads

Intro

My assembly language programming ability is very minimal, in part because I don't actually write many programs.  In order to create a working program I proceed with writing and testing code in very small increments.  This allows me to identify problems without looking through lots of code.  However it also means I have to assemble many iterations of a program before it is completed.

Ben Eater's original 6502 system required you remove the computer ROM from the breadboard, flash the latest program in an EEPROM programmer and reinsert the ROM each time a change was made to a program.  It was a great method to get started, and wasn't too painful when copying Ben's programs and testing them, but only allowed me to write the simplest of programs.

In BEN2, I put the ROM into a ZIF (zero insertion force) chip holder which made the removal / insertion task much easier, and avoided disturbing wires.  An Arduino mega was attached to Ben's computer to allow monitoring of address/data lines as instructions were executed.  I incorporated some simple changes to the circuit which allowed me to use an Arduino sketch to program the ROM in situ.  This was a much better solution and I have been using it to write programs on BEN2 and its predecessors for a long time.

I now have a new mechanism using a simplified xmodem protocol to download binary files from the PC to BEN2 RAM using the ACIA serial adapter.  It gives me the opportunity to make further improvements.  In particular I want the process to be simpler, require less intervention and be a bit faster.

Use a script

The tools for building and downloading an executable were as follows:

  1.  Edit assembly source program using Notepad++
  2. Put all the following activites as batch file commands
  3. Assemble the program using ca65
  4. Link the program using ld65
  5. Run my own C program "FormatHexProgram" to format the executable as an Arduino data statement.
  6. Use the Arduino-cli tool to compile a sketch containing the executable as data
  7. Use Arduino-cli to run the sketch which copies the executable to ROM
The new process utilising xmodem is already simpler:

  1.  Edit assembly source program using Notepad++
  2. Put all the following activites as batch file commands
  3. Assemble the program using ca65
  4. Link the program using ld65
  5. Run my own C program "serial5" to transmit the executable from the PC to BEN2
  6. Tell BEN2 to receive the program and store it in RAM at the specified address
Previously I called bash.exe in a batch file to execute linux commands in WSL.  Since ca65, ld65 and serial5 are all linux programs it is easy to invoke a shell script which then runs ca65, ld65, serial5.  I still prefer to use Notepad++ in Windows to create and edit source programs.  It is also convenient to put all the necessary parameters in a batch file and then call a shell script and pass across the values for processing. 


Use minicom

My 6502 + ACIA serial connection uses COM9 for terminal input / output.  Putty is a widely used terminal emulator which I have used for many purposes and I have been using it for my 6502 screen.  There is a slight irritation, when downloading to the 6502 using my serial5 program I have to "hangup" the putty session, then "restart session" afterwards.  I couldn't think of a way to automate this  pause/resume and the rest of my processing is in WSL so I thought I should try a linux alternative.
Minicom is a simple, free and widely used program and it is easy to configure it to use COM9.  Once it has been started it works in the same way as Putty so there seems no downside to changing over.
One bonus is that you can setup function keys F1-F10 in minicom using macros to play a sequence of keystrokes.  For example, I often use the "s2000" command in the 6502 monitor to run a program at address $2000.  I set up F1 with a macro to execute this command for me.

Share the COM port

When I tried running a serial5 download, with minicom running I found that it still worked.  Minicom doesn't mind sharing the COM port with a linux program.  In fact it does display the terminal bytes on the screen which is slightly messy.  Minicom uses Ctrl-A as its escape/command character; when sharing the port it wont interpret Ctrl-A as a command, it just sends it down the wire.  This protects the session from being corrupted by the binary data.


So now it is rather wonderful that I don't have to pause/resume the terminal, I can run the serial5 download without further action.
Of course I do have to make sure that the monitor is running and start the 6502 download with the x command.

Even more exciting, I can use the linux command line to type in command for the 6502.  The following screen shows a few linux command line inputs being executed on the 6502.  the "l" command is my favorite "sticky key" which checks that the screen is responding.  "m1000" displays a sequence of memory addresses. The "x" command starts a download.  This is wonderful; it means I can start the 6502 download from my script, I don't need to do it manually.  In fact, I just have to make sure the monitor is running when I assemble / download a new program to RAM.


An even more, more exciting option that I could experiment with is to add a write command to my monitor.  I could then use a WSL command something like
    "printf w <data.....> " /dev/ttys9"
which would store the data in RAM without any need for specialist download programs at all.
I haven't implemented this yet as my download process is working well, but the future may be wonderful.

Conclusion

I started this snippet of work as a tidy-up operation and it has developed very nicely.  I have a totally automatic process for building/downloading programs to RAM together with the ability to remotely enter commands from a script.  There is also a possibility to change / simplify my download process further with a new "w" monitor command.


Sunday, 4 June 2023

6502 : BEN2 : Xmodem Enhancement 1 : Relocatable programs

 Intro

A simple Xmodem-like download is now working and, so far is proving reliable.  This is great news and isn't something I have been able to achieve previously.  I now want to build up the basic system so it provides a solid, convenient development environment.

Load Address

Initially we have included ".org 1000" in our programs and they are compiled so that they will run properly when the xm subroutine loads them to address $1000 in RAM.

If we want to load the program at a different address, say $2000, we can change the assembly program to include ".org $2000" and change the xm subroutine to copy the executable into RAM starting at address $2000.

Clearly we dont want to have to change the xm subroutine each time we want to load a program at a different address.  The xmodem protocol includes a load address in the first two bytes of the first data packet.  If we put the load address ($2000) at the start of our executable we can modify the xm subroutine to use this value as the starting address for copying the executable into RAM.

Step 1 : Prefix executable with load address

Initially I started testing, using the existing load address $1000.
Linux allows us to easily create a two byte file with two hex characters values:
  printf "\x00\x10" > 1000.ram
We can then concatenate the two byte file with the executable to create an upload file starting with the load address
  cat 1000.ram via-3-LED-1x.x > a.ram

I had a neat idea to make the associated change to xm as simple as possible.  xm stores the start address in a two byte pointer ptr+ptrh.  Instead of initially loading $1000 into the pointer I loaded $0FFE.  This means that the address is loaded to $0FFE and $0FFF then the executable loads starting at $1000 and works as it did previously.

To allow different load addresses to be used I simply change xm to save the load address in ptr+ptrh then subtract two and read in the program as previously.  A slight complication is that subtracting 2 from the pointer is a 16-bit operation as the start address pointer is two bytes.  My assembly programming isn't very good but I found a helpful webpage with loads of useful snippets including 16 bit arithmentic


Step 2 : Telling the compiler the correct load address

Next I wanted a flexible way of changing a program load address.  I would prefer to change a batch file when I want to save the program at a new address rather than the program source.

Googling on the subject it becomes clear that putting a ".org" statement in the source is generally a bad idea.  It is the linkers responsibility to set the start address not the assembly process.
I removed the .org statement from my source program and added a switch "-S 0x2000" to the ld65 linker command.
I can easily add a variable to my batch file which sets the load address. 
The batch file also contains a small script to prefix the executable with the load address.

Results

The new setup works well.  I can specify in the batch file where I want the program to run and, after it has been downloaded I can call call the program from the monitor using "s1000" "s2000" etc.  At the time of writing I only had two xmodem downloadable programs, one to turn on LEDs and the other to display an LCD message.  I loaded up both of these into RAM, one at $1000 and the other at $2000 and then I could run either of them at will.

Somehow this feels like a big step forward.  I have a monitor in ROM and I can load programs into RAM wherever I like and run them independently whilst they remain loaded.

Position independent code

I did consider experimenting with Position Independent Code (PIC) for my assembly programs.  When I assemble programs without an ORG statement the addresses are put into instructions relative to the start of the module.  When the program is linked, the linker program calculates absolute addresses and puts them into each instruction as necessary.  When the program is loaded the addresses are already hard-coded in binary so that the program can run without calculating addresses/offsets.

In fact my first LED program, which just initialises the VIA and turns LEDs on is PIC.  There are no branches or loops and data storage is "nearby" and can be addressed using offsets from the current location.  Consequently I can assemble the program with any load address and the code generated is identical.  I could copy the program anywhere in memory and it should run fine.  Thinking about it there are probably some restrictions due to page boundaries; but I am trying not to deal with or worry about those at the moment.

However it is unusual to have a program without branches and with only local data, and what is needed is programming style which utilises all relative addresses and / or an assembler that can generate the appropriate code.
Some instruction sets, including RISC-V, have a base register which can be utilised in instructions to offset them all, but the 6502 doesn't.  Without that capability it would be difficult to provide PIC and the 6502 assemblers dont try.

Friday, 2 June 2023

6502 : AMI : Daughter board

Intro

We set up the ACIA serial interface as an add-on to the Ben Eater board BEN2.  It has proved to be very successful so far, serial download reliability has been very good.
It was originally my intention to develop a new board containing the ACIA; however the current solution seems good enough to proceed further.  On this basis I decided to replace the breadboard with a daughter board AMI which contains the contents of current add-ons.  Principally it includes the ACIA serial interface and the LCD screen.
Adding a 1MHz oscillator and stepdown speeds, some LEDs and buttons would also be useful.  This would enable me to dispense with two breadboards.

Board Design

First step was to review which board type is most appropriate for the daughter board. My first thought was to use breadboard style stripboard so I could just copy my breadboard setup for ACIA and LCD.  However the connections at both ends are not currently simple ribbon cables.

To make connection / disconnection reliable I wanted a header along the top of AMI with all necessary signals from BEN2.  The best option is clearly the same stripboard layout as used for BEN2.  It has 48 columns and 6 rows of 5 connections.  We can use the top row for headers, the second and third rows for ACIA and LCD, leaving some of row four plus rows five and six for the oscillator, LEDs, buttons and any other add-on chips.

As ACIA is the most vital component and the most sensitive I designed it first.  The picture below shows the header for both 6502/ACIA and VIA/LCD along the top.  The ACIA chip is on the left hand side.  Pins on the top row only need a single wire connected.  On the bottom row we have some extra connections for the oscillator.

For flexibility I put a header row below the ACIA so that I can use / monitor ACIA pins.  Also I put header pins on unused top row pins.  In the event that I decide to use hardware flow control I can utilise the ACIA pins using the headers.

My intention is to use a USB cable with FTDI built in so I added a 3-pin header below the ACIA for GND, TX, RX pins.  In future I may mount an FTDI board, so I have roughed out and reserved space for one at the bottom of the boad.

LCD connections to the header on AMI's top edge are simpler.  I added a female header to attach the LCD Panel to.

The partially complete AMI layout, with all connections for ACIA and LCD is shown below.


ACIA Build

The ACIA connections were added to the board first and successfully tested.  


Initial results were very good, in particular the dropping of characters output to the display seems to be reduced / eliminated.  this indicates that using the oscillator on the breadboard was a source of many problems.  Any future use of breadboard oscillators would probably benefit from a little carrier for the oscillator which plugged into the breadboard properly.

LCD, LED, Buttons

Encouraged by the ACIA working properly, I added some LEDs and buttons to the design.  The LCD utilises 3 pins on port A and 8 pins on port B.  That leaves 5 pins on port A I can use for other purposes.  I intend to use pins PA0 and PA1 for buttons and PA2, PA3, PA4 for LEDs.  It is always useful to have these simple inputs and outputs available to programs.  Status LEDs are rather dull but I made them slightly more interesting with Red, Amber and Green.  I also added a blue "power on" LED.  I dont currently have any button programs but they will be useful, particularly for testing interrupts.

Version 2 of the design adds a few components in the centre of the board.  I had to be carful to leave enough space around the buttons.  Although the button itself doesn't take much space, my finger uses a 5 x 5 square of space when pressing it.


I soldered the LCD connections and added LEDs and buttons.  When I had wired up the board  I was most concerned to ensure that the ACIA still worked.  It did continue to work and I was pleased that I could use xmodem to download LED and LCD test programs.

Both LCD and LEDs worked using the test programs so we are making good progress.

Remove Rats Nest Wiring

Of course the purpose of AMI is to replace the breadboard which previously contained ACIA and LCD.  As I started this stage my wiring was becoming quite messy with AMI providing the ACIA and the breadboard still containing LCD + LED.


Now that i have AMI working I can remove the ACIA/LCD breadboard and my computer is beginning to look better:


Clock and other bits

There are a few improvements I wanted to make to AMI.  Firstly, the buttons have no debouncing circuitry.  I added a capacitor across each of them to reduce bouncing - although it is not a complete solution.  I will need to add a debouncing subroutine before I could use buttons for detailed work such as counting.

Next I added a second reset button.  The circuitry is a duplicate of the BEN2 reset button.  It helps to have a reset button more accessible on AMI as the one on BEN2 is sometimes difficult to get right.

Thirdly I should add a clock chip to AMI so that I can dispense with a separate clock board.  Initially I thought I would add a scaling chip so that I can slow down the clock if I want to.  However I decided not to bother as long as I can still use the clock + 2 scalers or the Ben Eater clock if debugging requires it.  To achieve this I have a pin header adjacent to the clock chip.  The clock, clock+scalers or Ben Eater clock can be attached to the 6502 via a wire.


I soldered and tested these modifications but irritatingly BEN2+AMI stopped working when I wired the boards to gether again.   I disconnected all Arduino wires and the board became more reliable but not perfect.
It may be that I can set up the 6502 to flash its own ROM.  If so I potentially dont need the Arduino again.  In addition to flashing ROM its other purpose was to show single stepping, but I rarely need to do that now.

When I tested the clock chip on AMI it didn't work.  I soon found that its GND was soldered in the wrong place and needed to correct this.

Finally I recognised that my FTDI serial connection can power the board so I only need one USB connection from my PC which provides serial communication and power.  To make connections simple I added a 5V post next to the FTDI GND post and connected it to other 5V signals.
I was now able to remove the second "add-on" breadboard leaving BEN2 and AMI working together with only a single external cable required.  This looks a lot better.

Concluding Notes

Our 6502 hardware is now in great shape.  We have two boards containing all the chips we need.  We have terminal i/o and LED/LCD output.  We have a basic working monitor and we can download  executables from a PC to run them.
I am becoming confident that I have a system without the crippling problems of the one which was shelved in 2021 and believe it should become a good basis for further work.