drawing bitmaps in lc-3 assembly
this semester i am a teaching assistant for cs 252, introduction to computer engineering. to give the students a basic intro to instruction set architectures and assembly, we us the lc-3. since the lc-3 simulator is rather limited in the “cool” things it can do, we have decided this year to use an lc-3 simulator developed at penn called pennsim. the biggest reason for this is because it has memory-mapped video output, meaning we can write games, which are much more interesting to students than say a hungarian notation calculator (like i had to write in the equivalent course back at byu).
i’ve been tasked with writing a tictactoe game using pennsim, and then for the final assignments, i will give the students the code with some logic stripped out that they will have to rewrite. unsurprisingly, getting my brain to think is assembly, especially with such a limited instruction set, has been a bit of a task. so, my idea was to print the board as follows:
the white lines were easy enough, just a couple of for loops. but numbering each location on the board (so the player can easily select which position they want to play on using the keyboard) was kind of an interesting problem. i decided to store each number as a bitmap. so in my text editor, i wrote these for 1-9 and O and X (this is for 1 if you cannot tell):
0000000110000000
0000001010000000
0000010010000000
0000000010000000
0000000010000000
0000000010000000
0000000010000000
0001111111111000
then i typed up a quick python script to convert these bitmaps into hex numbers:
f = open("bitmaps.txt",'r')
lines = f.read().split("\n")
f.close()
f = open("hexbitmaps.txt",'w')
for line in lines:
if line is not '':
f.write(".FILL "+hex(string.atoi(line,base=2))[1:]+"\n")
else:
f.write("\n")
f.close()
which gave me the following for the previous bitmap:
.FILL x180
.FILL x280
.FILL x480
.FILL x80
.FILL x80
.FILL x80
.FILL x80
.FILL x1ff8
i copy and pasted the hex numbers right into my assembly code. by now i was feeling pretty proud of myself, until i realized how annoying the lack of useful instructions such bit shifts in the lc-3 was going make things. it took me a few minutes of thinking, but here is how i solved it: i created an array of bit masks like:
1000 0000 0000 0000
0100 0000 0000 0000
...
0000 0000 0000 0001
then i wrote a set of nested for loops. the outer loop incremented through each row (y) of the bitmap and the inner loop would AND each bitmask with the current row to determine if a pixel should be drawn at the current column (x). here is the lc-3 code for those who are interested:
AND R3, R3, #0
ADD R3, R3, #8 ;counter for the 8 rows in the bitmap
BMROWLOOP LEA R6, BITMASKS ;store the address of the bitmasks array
AND R7, R7, #0
ADD R7, R7, #15 ;counter for the number of pixels in a row
LDR R4, R1, #0 ;stores one row of the bitmap in R4
BMBITLOOP LDR R5, R6, #0 ;loads the first bit mask into R5
AND R5, R5, R4 ;AND the bit mask and the current row
BRz NOBIT ;if the result is zero, skip next instruction
STR R2, R0, #0 ;draw a pixel (R0 holds pointer to video mem)
NOBIT ADD R0, R0, #1 ;move over one column in the video mem
ADD R6, R6, #1 ;locate the next bit mask
ADD R7, R7, #-1 ;count down until all pixels in a row are done
BRzp BMBITLOOP
LD R5, VERTADD ;code to move the video mem pointer down a row
ADD R0, R0, #-16
ADD R0, R0, R5
ADD R1, R1, #1 ;increment to next row in the bitmap
ADD R3, R3, #-1 ;count down until all rows are done
BRp BMROWLOOP
probably not the perfect solution, but not bad for just banging away at it at 2 in the morning.