Showing posts with label Tutorials. Show all posts
Showing posts with label Tutorials. Show all posts

05/09/2018

Converting C into Assembler on the Amiga

Introduction

This article is basically a cheat-sheet for converting C code into Amiga Assembler. The basic method is not my own. I found a page on it a year or so back, but unfortunately, I forgot to save the link. This is my rendition of the method, and any errors in thought or application should be considered mine.

Most coders, whether on the Amiga or not, are familiar with the C language. Not everyone knows how to work with Assembler, though, and the bar for learning it can be daunting.

For people already conversant with C who want to learn Assembler, there's a solution: translating it yourself, by hand, at speed. That sounds like a tall order, and complex C is indeed hard to translate. But C doesn’t have to be complex in order to be powerful. If it turns out a reduced subset of C code can help to not only dip our toes into Assembler but to write it quickly and accurately (and I aim to demonstrate that it can), then it should be an avenue worth pursuing.

Our goal, then, is to write our program in C, and then render that code into legal 68k Assembler. At first glance that would be intimidating. However, by adding just a few self-imposed constraints, we can restructure the C code into a form less concise but much easier to translate.

This is a deceptively powerful technique. After all, compiled C is just the computer doing its uninspired best to write Assembler in the first place. It just does it much faster than a human, though sometimes less efficiently (particularly on older systems, where memory and CPU speeds impose constraints on the fancy optimizations the compiler can pull). Still, manual translation remains a proven method of producing a working Assembler program in short order. The added benefit is the greater insight into one's own code offered by manual translation.

From C to A in three steps

The procedure itself is unambiguous. It takes a C listing, and the end result is a rough sketch of the final yet-to-be-optimized Assembler code. We will most likely require a few Amiga-specific additions to get something up on the screen, but the resulting binary will otherwise be fully functional.

  1. Write the routine in C. Determine its correctness.
  2. Restructure the routine in accordance with the following constraints:
    1. Calculations must be on the form of [value] = [value] [sign] [number or variable] (ex: "x = x + 2"), nothing more complex.
    2. Comparisons must be on the form of [number or value] [comparator] [number or variable] (ex: "x != 2"), nothing more complex.
    3. Remove complex branching constructs. Replace with goto or function call.
    4. Remove loop constructs. Replace with goto.
    5. The only allowed conditional is a single-line if statement followed by a goto.
    6. Remove references to variable length strings, arrays or lists. Use statically sized arrays.
    7. Replace primitive functions with calls to OS functions on the target platform when convenient (ie. typedef).
    8. Rename variable names to resemble register names, where appropriate.
    9. Test that the code’s output is unchanged (i.e. is still correct).
  3. Translate this modified code into rough Assembler form, add Amiga-specific code (libraries, interrupts, hardware access) and, again, verify correctness.

How the method is used

To illustrate the method, we could try a simple example. Project Euler is a website that offers a series of problems in roughly ascending difficulty. Most of these problems are constructed in such a way as to make a brute-force solution computationally expensive.

I generally try to avoid spoilers. However, the very first Project Euler exercise should be considered reasonably easy for any CS student to solve in a handful of minutes. The Sieve of Eratosthenes provides a fairly good, conceptually simple solution. Better still, it yields a short enough listing to be manageable for us to translate. Note that will not translate the printout part of the routine, as it would just be a distraction.

Running this program yields a sum of 233168, which in hex is 0x38ed0. This is our expected result.

Next is to prepare the C listing for translation. Applying our list gives us the following modified C listing:

This is already pretty close to what we want, but we're still not there. We need to put registers in place of variables:

Except for the Printf() function, this is practically Assembler at this point. The instructions are now ready to be translated. The result:

Assembling the program results in a minor error, easily fixed by changing the addq instruction in the third loop to add.w.

As noted, the program should result in a value of 0x38ED0 in register d0. When we assemble the corrected listing and run it, it yields exactly that: ergo, the program works. The only bug we encountered stemmed from my attempt to optimize instruction size, which was not part of the method. Had I stuck to the script, the first attempt would have assembled and given the correct response.

Two things bear mention about our C implementation. When I wrote this, I declared three iterators (scoped to each for loop). Doing so is good high-level practice, but runs contrary to the Assembler ethos of porous scope. When preparing for translation, consider deliberately compromising scoped constructs or even declaring variables as global.

Regardless, the results speak for themselves. Judging by the above demonstration, the idea is sound.

14/06/2018

Incremental Tutorials #1: The Bootable Program

I grew up with my dad’s Amiga 2000. It was a brilliant machine that I desperately wanted to understand. The path was daunting, with countless steps and milestones along the way.

Although I did learn a lot, there were some I didn't manage. I kept circling back to one particular goal. The bootable disk. This was something the PC couldn't manage, and that for me somehow encapsulated what it meant to use the Amiga.

It's exactly what the name suggests. You place a floppy in the diskdrive, it loads, and then the game starts. I wanted to make such a disk. It was magic to me, the province of mighty game coders, and I was a kid dreaming the dreams of Icarus. I never did succeed, not back then. But as it turns out, it’s never too late to start.

Programs on the Amiga usually came in one of two categories. Either the program was meant to be used under Workbench (generally, that meant it was a utility), or it was designed to boot from floppy and would immediately kill the system in order to maximize those resources for running the program (this was usually the case for games). We will probably go into how to do that as a later refinement, but it all starts with this first lesson, which is booting from floppy.

Well, turns out it's not a difficult process. To that end, I’m making this our first incremental tutorial. It has but one, simple goal: to let you create a bootable disk containing a typical game or utility for the Amiga 2000.

This tutorial will be simple and rough. It will not be optimized in any significant way. The idea is to master the basic technique first, before we return and refine what we have into something smaller and punchier.

First iteration: the floppy that boots

Overview

This will be the first tutorial. The intended end result is a disk that will boot into an already existing program. This requires

  • adding a boot-block to the disk,
  • adding a program for us to boot, and
  • writing a suitable start-up sequence.

This is a reasonably straightforward task. I will stress that I'm assuming you use an emulated machine, as logistical issues of running on real hardware (like transferring ADFs to floppies, etc) won't be part of this tutorial.

Step One - adding a boot-block

All bootable disks start with a blank floppy. If you're using physical media, that's any old floppy disk you have lying around whose contents don't matter to you. If using an emulator, create a disk image such as an ADF file (UAE has a button for this).

Now, the next thing you do is boot your Workbench system disk (version doesn't matter). Once WB finishes loading, open a CLI window, and start typing.

format drive df0: name Lesson1_1

What follows is a prompt to format the disk, so pop out the Workbench disk. Insert the empty disk (making sure it's not write-protected), and hit return. Formatting will, unsurprisingly, wipe the disk, erasing all its data and leaving it blank. Next, we want to ensure the disk boots when you insert it into Df0: on startup. After the drive's settled, type:

install df0:

You'll be asked to swap the disks, but the program itself takes no appreciable time to run. That's basically it; you now have a disk that autoboots. Go ahead and try it by pressing CTRL+Amiga+Amiga. Once you're done, boot back into Workbench.

Step Two - adding a program

The idea here is to start a piece of code that doesn't need running under Workbench. My rationale for that is the fact that bootable disks often avoided using things they didn't need. In other words, we don't want to tie ourselves to having a running Workbench process. Following this stripped-down ethos, we want a dead simply program, one that doesn't use extra libs or indeed more than one executable. It seems pleasingly symmetric to use Pong for this, so we will: this PD version of Pong by Claudio Buraglio from 1991 satisfies all the conditions.

If you're running an emulated Amiga, transferring the binary is just a matter of loading the ADF like any disk. A real Amiga makes it a bit harder: I would recommend the PCMCIA to Compact Flash solution, a Gotek drive, or (if you're feeling old-school) a null-modem cable. Transferring it to a real machine can be nontrivial, but, again, beyond the scope of this tutorial.

Next, copy the Pong file to the disk.

Step Three - adding the startup-sequence

The final step is eminently simple. Create a folder named "s" on the Lesson1_1 disk. Whenever a disk boots, AmigaDOS checks the folder named "s" to see if it contains a file called "startup-sequence". As it's empty at present, we need to create that file.

Open a new CLI window. Type the following (assuming the disk is in df0:):

ed df0:s/startup-sequence

This should create a startup-sequence file. Type

Pong

, then an asterisk, then confirm by pressing Y. This will save the new startup-sequence.

Congratulations: you have an autobootable disk.

Conclusion

We now have a disk that will autoboot and run an existing program. It's still a systems-friendly program, and can be run under Workbench. The common OS startup routines are still in place. But we can boot.

If you're anything like me, you may be underwhelmed at this point. "What? It's that easy?"

On the one hand... yes, yes it is. On the other, though, 'easy' was really the point of this exercise. Every journey has to begin by one single step.

13/06/2018

Incremental Tutorials

This blog has lain silent for a while. I’ve been busy with real life, because real life must take precedence when you have work and children to consider. The main reason, though, was that I just couldn’t decide on how to finish the tutorial. The bar for learning graphics programming seemed just too high (because of course, I’d resolved not to resort to AMOS, which would have made it trivial). No book I read on the subject seemed to truly have what I needed, or to fit my setup, or be low-level enough for what I had in mind.

So there I sat, wondering why I couldn’t make headway. I felt stupid. I questioned if I was even smart enough to begin to understand the subject, let alone write about it authoritatively.

Eventually, I realized that the problem wasn’t intelligence or understanding, but that the underlying logic behind my tutorials was lacking. So I decided to change my approach. My original format was that of the typical tutorial: “this is what you want to do, here’s how you set things up, these are the steps you must take, and you’re done.” When written well, that gives you a good solution for achieving the goal at hand.

On reflection, I wanted something different. The standard tutorial is sequential. I don’t really want that: what I really want to do is to describe a creative process, and that process (at least for me) is not sequential but iterative.

Enter the incremental tutorial. Each tutorial consists of a number of self-contained posts. For each post, we add or modify one piece of functionality, while producing a valid product that does something. That means that each tutorial will have few steps, be of manageable simplicity, and that there will always be a checkpoint close at hand to retreat to, should anything go wrong.

I think this is a promising concept. It remains to be seen whether it's enough to take it all the way.