A 16 bit softcore processor: Software

      No Comments on A 16 bit softcore processor: Software

I settled on CustomASM for the re-targetable assembler for my processor, after briefly playing about with the somewhat unconventional axasm.

CustomASM allows you to define a processor’s instruction set in a configuration file and then assemble assembly source code using that definition.

This is easiest to explain with an example. Here is the definition of the LOADI instruction:

load.{width:loadtype} {dst:reg}, #{value} -> 
    0b000100 @ width[1:0] @ 0b00 @ 0b000 @ dst[2:0] @ value[15:0]

width and dst are tokens of the given type. For instance reg is defined as follows:

#tokendef reg 
{ 
    r0 = 0 
    r1 = 1  
    r2 = 2 
    r3 = 3 
    r4 = 4 
    r5 = 5 
    r6 = 6 
    r7 = 7 
}

Whist the loadtype token is defined as follows:

#tokendef loadtype 
{ 
    w = 0b00 
    bu = 0b10 
    bs = 0b11 
}

From the instruction definition and the two tokens, the following line:

load.bu r7,#0x1234

Generates the following word pattern:

0b000100 0b10 0b00 0b000 0b111 , 0x1234

Or:

0x1207 , 0x1234

It’s pretty neat. CustomASM supports all the usual assembler things built in, including labels (the pc built in variable refers to the current instruction), comments, constants etc. Some of the syntax it uses is a bit strange, for instance literal strings are inserted into the output via #str instead of the more common .ascii. But overall, it’s a very powerful tool and has allowed me to very quickly define the entire instruction set for my little processor. Tokens can appear at any point on the source line allowing all the ALU operations (register used for operand, immediate used for operand, no operand) to be defined by just defining the three opcodes (ALUM, ALUMI, ALUS).

In terms of building up a useful system, shown in the video being used to play Snake, there is a collection of pieces needed in addition to the processor itself:

  • VGA interface
    • Tile memory
  • Seven Segment display driver
  • Button and switches interface
  • PS/2 interface
  • System/video memory

I have not yet created a git repository to hold anything other then the processor component, mostly because the parts outside of the processor are not generally useful, unlike the processor.

The buttons, switches and seven segment display driver are all completely trivial and don’t warrant much discussion.  Each presents 16 bits of data either for input or output.

The PS/2 interface is an almost entirely unmodified copy of the PS/2 driver originally written for the 68000-based MAXI000 project. It presents a status register and a read/write data register.

The most interesting aspect is the video sub-system.

It is based on what has been previously written but with several changes:

  • The 80 column by 60 row text mode is available
  • A new 40×30 tile mode has been added for the Snake game, switchable via a register write
    • Tiles are 16×16 pixels, in 16 colours
    • The format of the tiles is packed nibbles, ie. a byte describes two pixels. This is held in a private memory area, similar to the font data used to hold the 8×8 text font

Palette lookup is done in VHDL code, ie. it is hard coded.

The video memory, whether it is used to hold the font index for the text console or the index into the tile data, is dual ported: it is read and written by the processor, and read by the video generator. This is an extremely useful feature of FPGA embedded memory, and  makes programming the video generator in the FPGA much simpler then the private memory bus used on the MAXI000.

Generating the actual tile data for the Snake game was an interesting challenge. The starting point was an online sprite painting system, piskel:

This website allows you to draw sprites and output them as C code, which was then converted (using a wrapper C program and a perl script) into MIF data. The font data was converted from a colour 16×16 font I found online.

The Snake game itself then.

The mechanics of the game are essentially identical to snake as written for the 6809 a few years ago. The core mechanism of the game is two arrays, one for the rows and one for the coloumns, of each snake segment. Counters hold the position of the head of the snake and its length. As the snake moves around the screen, the head positions are written to the next array elements and then drawn, and the tail square cleared using the length variable.

Rather then talking in depth about the game mechanics, it is perhaps more interesting to look at how the processor, with CustomASM’s help, can be used to implement some relatively complex routines.

drawplayarea:   clear r0                ; AKA TILE_BLANKx2
                load.w r1,#WIDTH*HEIGHT ; number of rows
.clearloop:     decd r1                 ; clearing words
                store.w (ORIGIN,r1),r0  ; blank it
                branchnz .clearloop     ; more?

This routine zeros WIDTH*HEIGHT bytes, writing in words. Note the register with displacement (ORIGIN) in the store instruction. The zero write sequence will be done from the end of the memory block down towards the beginning. This is quicker then counting up and comparing with the size. Also note that labels starting with a dot are local labels.

getps2byte:     load.bu r0,PS2_STATUS   ; get from the status reg
                test r0                 ; nothing?
                branchz .nothing        ; keep waiting....
                load.bu r0,PS2_SCANCODE ; get the scancode
                compare r0,#0xf0        ; key-break seqence
                branchz .nothing        ; yes? exit early
                store.w SEVENSEG,r0     ; output the scancode (fun)
                return                  ; done
.nothing:       clear r0                ; nothing? set 0 in r0
                return

This routine is used to poll the PS/2 port. If a keypress-related scancode is available it returns it in r0, otherwise r0 is set to zero.

The first thing this routine does is check the PS2_STATUS register. If there is no pending byte, zero is returned. Otherwise the byte (scancode) is extracted from PS2_SCANCODE. The break (release) scancode escape (0xf0) is treated as an uninteresting event, and zero is returned if it is found. Otherwise the data is presented on the seven segment display (for diagnostics, and as a quick way to find out what scancodes certain keys have) and the value is returned in r0 to the caller.

The full sourcecode for Snake is available on github, for anyone interested.

I can’t thank the author of CustomASM enough. It’s an amazing tool for projects like mine. The alternative would have been to hand write my own assembler, a doable, but not especially enjoyable job.

Though I have grand plans for my processor, in my next post I will return to finishing off the MAXI000 build, starting with the SIMM slot…

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.