My latest project is an assembler for the PIC microcontroller. I figured it'd be an interesting project to embark on, and since I have a whole bunch of the microcontrollers lying around my place, it'd be something I could actually use.
One of the things that makes writing an assembler for the PIC interesting is that there are a few different instruction sets depending on the series you're using, the 18 series have 16 bit instructions and the 12/16 series have 14 bit instructions for example. Because of this, I wanted to make it so an instruction set was something that I could define easily:
(definstructionset pic16-instructions
((addwf :args (f d)
:description "Add W and f"
:opcode "00 0111 dfff ffff")
(andwf :args (f d)
:description "AND W with f"
:opcode "00 0101 dfff ffff")
(bsf :args (f b)
:description "Bit set f"
:opcode "01 01bb bfff ffff")
...))
I decided to describe the opcodes as strings like that, as thats how they are defined in the official data sheets:
Eventually, I'd like to have a script that you could pass the datasheet PDF and it would auto generate the instruction set descriptor.
From that point a macro converts the instruction specs into a hash table of instruction symbols to instruction-spec classes:
(defclass instruction-spec ()
((mnemonic :accessor spec-mnemonic
:initarg :mnemonic)
(description :accessor spec-description
:initarg :description)
(opcode :accessor spec-opcode
:initarg :opcode)
(base :accessor spec-base
:initarg :base)
(byte-specs :accessor spec-byte-specs
:initarg :byte-specs)
(args :accessor spec-args
:initarg :args)))
The base slot of the class is an integer with all the parts of the opcode that are static (e.g the 0101 of the BSF instruction), and the byte-specs will be a list that maps instruction arguments (f, b, etc) to their respective parts of the opcode.
What's next? A way to tell the assembler about the different memory maps of PIC. This will be so that the assembler will automatically be aware of the all the PIC control registers without having to bring in / create an include file for each processor.
And from there the actual assembler/evaluator, right now I'm thinking that it'll be used like this:
(assemble :target pic16f877
:fuses (NO_WATCHDOG NO_CODE_PROTECT)
((:start 0) ;; label with explicit start address
(goto setup)
(:interrupt-service-routine 4)
(goto isr)
(:setup
(bsf latb 2)
(movlw (* 6 7))
(movwf LIFE)
(some-function-call some-arguments)
;; the output of the function called will be evaluated, like
;; macros.
...))) => #
;; and a way to turn the assembled output to into s-records ready for
;; a pic programmer.
Until next time...
Edit: Fixed typos, remind me not to blog without sleep.
Yet another code monkey from London, living in LA.
Links