Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework PyBeeb into a reusable system, with host interfaces #5

Open
wants to merge 53 commits into
base: master
Choose a base branch
from

Conversation

gerph
Copy link
Contributor

@gerph gerph commented Dec 23, 2023

Summary

This is a large-scale reworking of the PyBeeb sources to make it into a system that can be plugged into other projects. The intention has been to make it easier to work with, be hooked to provide support for debugging functions and be able to be used for small exercises.

Significant changes

  • The bulk of the code has moved into a package,pybeeb, which should be able to be used as a library to implement the 6502 system alone, or a 6502 with BBC micro interfaces, as required.
  • A unicorn-engine-like interface has been created to allow the system to be used more easily by people familiar with that interface.
  • A couple of small fixes for the interpretation of instructions, where they were found to be incorrect (not much - it was already pretty solid).
  • A new 'host' interfaces package within pybeeb provides BBC MOS abstractions for interfaces, allowing the MOS to communicate with the host system. These have been written with minimal knowledge of the innards of the PyBeeb system, in the hope that they will be retargetable to other emulators (a bit of an aspiration, but it'd be nice if other Python 6502 systems could use these).
  • Hooks have been added, both using the simple registration method used by the original code and the Unicorn-like hooks. Hooks allow code execution and memory accesses to be trapped. These traps can be used to either provide diagnostics for the system's behaviour, or to trap execution and perform different behaviour - these hooks are used to interface with the host interfaces package.
  • More complete disassembly has been added, and using the hook system this can trace execution.
  • Console interface for terminals has been taken out of RISC OS Pyromaniac and integrated with the host interfaces to allow input to the system, which should handle control keys on Linux. Windows interfaces haven't been tested in this project, be had been working previously.
  • Readline interfaces have been added through the host system, rather than through the MOS (can be disabled, but it could be used to replace the simple input in Python with GNU ReadLine/BSD LibEdit functionality in the future).
  • Filesystem access through the regular FS interface has been provided, although this is somewhat limited. Directories are supported, and the code attempts to map through filesystem naming sensibly. In the future, .INF file support could be added, to allow archives to be read more easily.
  • Simple tests have been added, and executed through code coverage. This isn't ideal, but it provides a visual indication of how the system is working. Coverage is currently just over 50%, which isn't ideal, but at least we know.
  • Python 2 and 3 support has been added, although it's very much a Python 2-like code base.
  • GitHub actions run the tests on push.

Implemented MOS interfaces

  • OSWRCH can write characters to the TTY.
  • OSRDCH can read characters from the TTY.
  • OSWORD 0 can read lines from the TTY.
  • OSBYTE &7F can check for EOF.
  • OSBYTE &81 can read a character with a time limit.
  • OSBYTE 0 can output a different version message.
  • OSFILE supports loading, saving, read and writing file information and deleting files.
  • OSFIND supports opening and closing files (but doesn't support closing all files).
  • OSBGET and OSBPUT support reading and writing streams.
  • OSARGS supports the file and extent operations, but not the flush or CLI args.
  • OSFSC supports EOF and *Cat but none of the other operations.
  • OSCLI supports *Dir and *Quit.

Other operations are not supported, including OSGBPB.

Example usage

charles@phonewave ~/projects/RO/pyromaniac/PyBeeb (make-into-pluggable-system)> python RunBeeb.py

BBC Computer 32K

BASIC

>*fx0 

OS 1.20 (PyBeeb)
>*dir tests
>*.
Dir.   $.tests

hello-basic  WR/WR       
HelloWorld   R/R         
ReadFile     WR/WR       

3 files
>LOAD "helloworld"
>RUN
Hello world!
>LIST
   10PRINT"Hello world!"
>*QUIT

The above is available as a video: https://asciinema.org/a/628615

The files appear to be formatted for DOS line endings, and had trailing
spaces on some lines. These have now been stripped with:

  for i in *.py */*.py ; do
      echo $i ; sed -E -i '' 's/ *\r?$//g' $i
  done
There's now an interface that's similar in style to the Unicorn
interface. The intention is that whilst this won't be a drop in for
Unicorn, the expectations that you might have from Unicorn, such
as the means of accessing registers and memory, and executing code,
are similar.
In order to be reusable in other systems, it is better if the whole
emulation system be in a package, rather than at the top level of the
namespace.

This change moves everything into a `pybeeb` package.
The tests hadn't been updated to run in the new packaged structure.
The PyBeeb, PyBeebU and test files are now executable under unix systems
and given shebang lines.
The execution hook was happening before the start of the decode
because there wasn't an easy way to inject after it. This meant that
it was not possible to report the instruction length. It might not
be a big problem, but it did mean that we couldn't call the hook
as easily. We now call the hook with the right details by having
a class that inherits from the Dispatcher do the hook calls.
The last address in the memory map regions would not be supplied - the
check for whether the memory was in the mapped object was not allowing
for the inclusive end address of the mapping. This meant that the
IRQ entry point (which is used by BRK) would be called with the high
address byte being returned from the standard memory, which was 0.
Instead of calling &DC1C, it was calling &001C.

We now check the memory region inclusively.
The instruction exceptions are now standarised to use an Exception
for the instructions, rather than a BaseException. The undefined
instructions are also reported as exceptions now, as well.
The __repr__ for Registers now includes their values.
The readBytes and writeBytes interfaces hadn't stopped when they reached
the end of the requested region because of a failure to account of the
end of mapped regions.
The register writes in the PyBeebicorn interface were always being
AND'd with 255, even for the PC. The value stored is checked in each
writer function now.

The execution hooks are now able to reset the PC value when they are
run, preventing the execution of the instruction that was decoded.
The BRK now returns the address to call, and reset has a variable
instead of just calling a magic address.
The RDCH entry point is replaced by a key reader from the console.
This uses the console code that I wrote for RISC OS Pyromaniac,
modified to be independant on Pyro.

This allows the RDCH entry point to be modified to handle the key
input. It's not great because we lose *Exec handling, but since we
don't have a filesystem or any other interfaces, that's not a big
deal right now.
The timeout value of None means 'wait forever', but it wasn't actually
working because it had never been tested. We now check this more
carefully.
A debug had been left in the PyBeebicorn interface when the PC
was set.
If there's no input, we could return None. This might happen if there
had been input but no characters were actually read, or the timeout
occurred.
We can now delete characters in the character input handler, and
they will be output properly.
The memory access hooks allow us to report when reads or writes happen
to memory. They may be problematic at the moment as the store operations
appear to read the values from the memory as well, before they then go
and store to the locations.
The RDCH function was not reading the escape key properly, because
we weren't setting the C flag. It also appears that unless you set
the error condition in zero-page, the system won't report the error
properly.
I had originally cut down the console.py to be a limited, POSIX only,
version of the console.py implementation. However, there's no reason
why this cannot be used on Windows so I may as well include the
Windows version. It should work reasonably well, but it's not as
polished as the POSIX version.
There is now a simple disassembler (more than the original one) in
the PyBeebicorn code, which can disassemble the code as it is
executed. This can be used in a hook to trace only certain sections
of the code.
We can now exercise the code in some tests that can be run in a
Makefile. Only the coverage run is explicitly supported right now,
and it'll fail if the tests fail, so it's a combined coverage
and test reporter. We mix the unit tests and the integration tests
at the moment, and the output from the integration test isn't
checked for regressions.
There is a simple workflow here to run the coverage and tests on
push. We're only running under Python 2, still, but it should be
sufficient to make it work for us.
The PyBeepicorn example program now has some more abstracted interface
entry points. These are set up in such a way that we can use them
without (in theory) having the knowledge of the underlying system.
They should be easy to extend for the other APIs.
We now have a very simple OSCLI handler so that we can trap *QUIT and
exit the emulator. If we don't recognise the command it is passed
to the rest of the handler system.
We now have classes which are hookable so that we can replace the
OSBYTE and OSWORD implementations. This allows us to provide some
interfaces to the host system which override the BBC implementation.
The host interfaces have now moved into a package so that they can
be used more generically if people want. Specific routines that
interface with the host are separate from the base implementations
so that they don't have to be the way that you communicate with
the system.
The filesystem interfaces OSFILE, OSARGS, OSBPUT, OSBGET, OSFIND
have been given base implementations which dispatch to different
routines. They're not especially well suited to being overridden
because each class is independant. Ideally we'd allow a common
interface to be provided for the filesystem, which those classes
could access.
The base OS interfaces were duplicating some behaviour and not as
common as you might like. The interfaces are a little easier to
update now. It's not clear that they're actually going to make for
a better general experience but they will allow some more
flexibility.

Also a few typos fixed.
OSGBPB's byte operations are now implemented in a base class. The other
GBPB operations that handle the media name, and directory enumeration
are not yet implemented.
When the timeout is absent, we weren't handling the cursor key
decoding. This meant that they came out as escaped VT codes instead
of BBC style codes.
Most of the operations have now been updated so that they work with
Python 3, allowing us to run the commands safely with Python 3.9 at
least. The code still works with Python 2.7 as well.
We now have the dispatcher handling OSGBPB calls for directory names
and the reading of filenames.
The OSFSC base implementation is now provided.
We now have a host filesystem module, fsbbc.py, which provides a
BBC-like interface to the host filesystem. It's not tested beyond
being run through a few simple exercises, but the name translation
is based on code from RISC OS Pyromaniac so it should have the
right style of thing in it.

The OSFILE load and OSFILE save operations are implemented and these
allow BASIC to load files and save them.
We now have the read/write file information and their variants,
and the delete operation, implemented in the host filesystem version
of OSFILE.
OSFIND, OSBGET and OSBPUT are now implemented. This should hopefully
do the right thing for us, although they're not well tested.
The data mode for ADC indexed Y access was listed as `imy` instead
of `iny`.
We now implement some of the stream handling in OSARGS, allowing us
to manipulate the pointer.
The .pyc files can be cleaned away to make them get rebuilt. Useful
if we're changing content.
The hosttty.py file is there to operate only on the TTY, so we might as
well name it as such.
Reading a key within a time limit is now supported by the INKEY
OSBYTE call.
The EOF check now works, either when called through OSFSC or OSBYTE.
Two hex digits in the code address for OSARGS had been transposed,
meaning it was never called. And the EXT handler hadn't assigned the
right variable.
Buffering and *Exec are handled as part of OSRDCH, but we need to
process the routing a little later, after all the code which retrieves
from the file, and checks the buffer. That way, if there is buffered
data, we'll use it.
The PyBeebicorn interface is now a main interface, with the name
Emulation, instead of PyBeebicorn. This allows it to be passed
around more freely. As such, instead of referring throug the regs
and memory objects which are passed in, we now pass through the
emulator obejct `pb`.
We now have a MOS interface that allows us to call 6502 code which is
necessary to write characters to the display. And we use this with the
*CAT interface. We also have a CWD in the filesystem now.
We now support *DIR properly, and we have a number of fixes for command
line uses.
Python 3 support had been broken when the CLI handling had been
reworked.
The documentation has been updated to reflect how the code has changed
to focus on the new hooking system and the ability to interact with the
host system. The old code has been moved to PyBeebMinimal.py as part
of this.
Coverage runs of the Memory tests hadn't been working at all. They were
not invoking the unittest on the module. We now correctly invoke that
and get better coverage because of it.

The coverage had been confused on MacOS because PyBeeb.py and
pybeeb/__init__.py are effectively the same module, so we were getting
other references to modules that weren't really there. To avoid this
problem, and to make it easier to understand the difference between
the tool and the package, the command line program has been renamed
to `RunBeeb.py`.
The filesystem interfaces should now be reporting the filenames
properly to the BBC and back out again to the host filesystem,
when used in both Python 2 and Python 3.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant