Wednesday, March 14, 2018

Macro Directives???

Before I get to the final(ish) article in the creation of the assembler, let's quickly take a look at the state of my emulator as a pie chart. Why a pie chart? In Canada it is PI Day as I post this. I have started some work on the TIA emulation but have a ways to go, but as there are a few months worth of articles on writing the 6502 emumation this is not a huge deal. Work is slower than I would like as I am getting way too many things on my plate again and things are probably going to get worse so I may switch to a fortnight or even monthly release schedule for this blog once the 6502 emulator section has been posted depending on how far ahead I am at that point.




At this point I have everything necessary to write the assembly language tests for implementing the 6502 emulator. My assembler is also in a good enough state that it would be adequate for the 2600 portions of my emulator as well as Coffee Quest 2600. The only features that I can think of that my assembler doesn’t support are multiple files and macros. As I am planning on having this on a web page which wouldn’t necessarily have access to a file system (there are workarounds) this is not going to happen within the assembler. While macros are not a requirement of the assembler they are handy to have as they can save some work.

A macro would need to be recorded using .MSTART and .MEND directives and would be inserted back into the assembly language with the .MACRO directive. To make macros more useful, parameters would be desired so I am reserving P0 through P9 as parameter variables for the macro so that when you issue a macro it would let you specify parameters which would be replaced in the code so you could have something like the following program:

.MSTART DELAY 255
     LDX #P0
delayLoop:
     DEX
     BNE delayLoop
.MEND

.MSTART LONGDELAY 2 255
     LDY #P0
longDelayLoop:
     .MACRO DELAY P1
     DEY
     BNE longDelayLoop
.MEND

.MACRO DELAY 1
.MACRO DELAY 3
.MACRO LONGDELAY 10 100

This demo clearly shows how macros can be convenient but also demonstrates a problem with just copying and inserting the code. The delayLoop will end up being created 3 times and the address linkage will be totally incorrect for two of the three instances. This means that the macro will need to be smart enough to be able to track which labels are internal to it and change link labels to point to the updated version. This can be done simply by having a separate class that manages macros which searches for label declarations and tracks them in a list. When executing the macro, it then looks for any reference to that label (both the original declaration and any link labels) and changes them adding a prefix.

The AssemblerMacro class is a bit too long to cover in full so lets just look at the relevant parts. As you can see, the code for adding a macro line also handles the detection of the .MEND directive. The idea then is that when a macro is started, all further token lines are sent to the macro being created until the end is detected.

fun addLine(line:ArrayList<AssemblerToken>):Boolean {
     if (line.size < 1)
           return false
     // TODO do we want to do a more thorough check here? Probably should allow anywhere on line?
     if ((line[0].type == AssemblerTokenTypes.DIRECTIVE) and (line[0].contents == "MEND")) {
           return true
     }
     if (line[0].type == AssemblerTokenTypes.LABEL_DECLARATION)
           labels.add(line[0].contents)
     macroLines.add(line)
     return false
}

Executing the macro simply searches through the tokens replacing the labels that are internal links or parameters with the appropriate label, as demonstrated.

val token:AssemblerToken = adjustedLine[cntrToken]
if (token.type == AssemblerTokenTypes.LABEL_DECLARATION)
     adjustedLine[cntrToken] = AssemblerToken(AssemblerTokenTypes.LABEL_DECLARATION,
                prefix + token.contents, token.num)
else if (token.type == AssemblerTokenTypes.LABEL_LINK) {
     if  (labels.contains(token.contents))
           adjustedLine[cntrToken] = AssemblerToken(AssemblerTokenTypes.LABEL_LINK,
                     prefix + token.contents, token.num)
     else if (paramNames.contains(token.contents))
           adjustedLine[cntrToken] = params[paramNames.indexOf(token.contents)]
}

The prefix string is simply a count of how many times the macro has been executed. Once the tokens have been adjusted so they have the correct labels and parameters we simply pass that array of tokens to the assembler’s parse method as if it were sent from the assembler after tokenizing.

.MSTART works by creating an instance of the AssemblerMacro class and passing in any default parameters that were set in the call. There is a macroInProgress variable which is a nullable variable so when it is not null we know a macro is being recorded and the parse method calls the add method of that instance to add any additional lines of tokens until the add method returns false at which point the macroInProgress variable is added to the list of macros and macroInProgress is set again to be null.

if (macroInProgress != null) {
     if (macroInProgress!!.addLine(tokens)) {
           macroList.put(macroInProgress!!.label, macroInProgress!!)
           macroInProgress = null
     }
     return arrayOf()
}

The .MACRO function simply looks to see if there is a macro using the indicated name. If there is then it copies over the rest of the tokens on the line as parameters to the macro then calls the macro’s execute method which “plays” back the recorded tokens using the indicated parameters.

There are all sorts of additional features that you may want to have with a macro system but for my emulator project, and probably even for my Coffee Quest 2600 project, this is more than enough of an assembler for my needs and it is built right into my emulator! If the assembler was going to be for production use, I would want to go through a few more hardening passes to make sure that it is robust and stupid mistakes won’t crash it. I would probably also want to add some advanced features such as mathematics support. I wouldn’t mind going over the code to do a bit of clean up and refactoring to make things smoother and more efficient. This is meant to be an internal tool for making the test code for the emulators (6502 and TIA) easier.

So finally, we can start on the actual 6502 emulation, which will be next week. As there are 56 instructions this will take a while to do. My plan is to break the instructions down into groups of related inststructions, cover the theory then implement as many instructions per week as space permits trying to keep each post down to a few pages.

No comments:

Post a Comment