Close

The assembler

A project log for Semyon

A small simon game using the 8 pin STC15F104W, written in 8051 assembly using the SDAS assembler from the SDCC toolchain.

HummusPrinceHummusPrince 10/14/2019 at 18:523 Comments

Now that I took care of the hardware, it's time that I'll work my toolchain.

As mentioned, I want to use an open-source toolchain, and SDCC looks like a good choice. The suite has an assembler called SDAS, a linker, and some other stuff. As I want to use assembler, I must tackle SDAS.

SDAS is said to be based on the ASXXXX suite of assemblers which supports a hell lot of architectures. Still, I found little to no examples of use, and as it raises errors for sources that work on vanilla 8051 assemblers such as A51, I had to find other kinds of information.

 For more information, I found a webpage with documentation for the original ASXXXX assembler. Specifically, I found the directives page very enlighting. But alone it's not enough for me to write an assembly code from scratch.

One thing I did was to compile a C file using SDCC and look at the output .asm file. So I've written a basic blink that looks somewhat like this:

#include <stdint.h>
#include <8051.h>

void main() {
    uint16_t i;
    while(1){
        for (i = 0; i == 0xFFFF; i++){}
        P3 ^= 0x04;
    }
}

 This code was able to compile, but the resulting .hex file did not blink the LED. I probably haven't done it right, as SFRs may need special attention.

Lets look at the resulting assembly:

;--------------------------------------------------------
; File Created by SDCC : free open source ANSI-C Compiler
; Version 3.9.0 #11195 (MINGW64)
;--------------------------------------------------------
    .module blink
    .optsdcc -mmcs51 --model-small
    
;--------------------------------------------------------
; Public variables in this module
;--------------------------------------------------------
    .globl _main
    .globl _CY
    .globl _AC
    .globl _F0
...

 The first thing is defining a module. After it is some special comand for sdcc. Then there are a whole lot of global variables, corresponding to special bits and registers. Note how directives start with a dot sign, unlike vanilla assemblers.

Then came this:

...    
    .globl _SP
    .globl _P0
;--------------------------------------------------------
; special function registers
;--------------------------------------------------------
    .area RSEG    (ABS,DATA)
    .org 0x0000
_P0    =    0x0080
_SP    =    0x0081
_DPL    =    0x0082
_DPH    =    0x0083
_PCON    =    0x0087
_TCON    =    0x0088
...

 It declares something as an area, probably calling it a registers segment, with the ABS and DATA parameters. The ABS flag means, as I have learned later, using absolute locations for the code, thus the .org 0x0000 directive after it means that this segment of code starts at 0th address. Dunno about the DATA flag though. However it doesn't seem important, as this part only looks like a '#define' section.

Lets move on. The following lines contain an awful lot of these directives, without any real code, until we find the interrupt vector:

;--------------------------------------------------------
; interrupt vector 
;--------------------------------------------------------
    .area HOME    (CODE)
__interrupt_vect:
    ljmp    __sdcc_gsinit_startup
;--------------------------------------------------------
; global & static initialisations
;--------------------------------------------------------
    .area HOME    (CODE)
    .area GSINIT  (CODE)
    .area GSFINAL (CODE)
    .area GSINIT  (CODE)
    .globl __sdcc_gsinit_startup
    .globl __sdcc_program_startup
    .globl __start__stack
    .globl __mcs51_genXINIT
    .globl __mcs51_genXRAMCLEAR
    .globl __mcs51_genRAMCLEAR
    .area GSFINAL (CODE)
    ljmp    __sdcc_program_startup
;--------------------------------------------------------
; Home
;--------------------------------------------------------
    .area HOME    (CODE)
    .area HOME    (CODE)
__sdcc_program_startup:
    ljmp    _main
;    return from main will return to caller

Behold, a reset vector! It makes an LJMP to initialisations, which came out null for this piece of code. When it's done, it LJMPs us to the '__sdcc_program_startup' label which directly jumps us to the main function. This is probably akin to the '_start()' function of GCC.

Note how this time all the .area directives say CODE, rather than DATA.

Here comes the real fun:

;--------------------------------------------------------
; code
;--------------------------------------------------------
    .area CSEG    (CODE)
;------------------------------------------------------------
;Allocation info for local variables in function 'main'
;------------------------------------------------------------
;i                         Allocated to registers r6 r7 
;------------------------------------------------------------
;    blink.c:7: void main() {
;    -----------------------------------------
;     function main
;    -----------------------------------------
_main:
    ar7 = 0x07
    ar6 = 0x06
    ar5 = 0x05
    ar4 = 0x04
    ar3 = 0x03
    ar2 = 0x02
    ar1 = 0x01
    ar0 = 0x00
;    blink.c:14: for (i = 0; i == 0xFFFF; i++){
00111$:
    mov    r6,#0x00
    mov    r7,#0x00
00106$:
    cjne    r6,#0xff,00101$
    cjne    r7,#0xff,00101$
    inc    r6
    cjne    r6,#0x00,00106$
    inc    r7
    sjmp    00106$
00101$:
;    blink.c:17: P3 ^= 0x04;
    mov    r6,_P3
    mov    r7,#0x00
    xrl    ar6,#0x04
    mov    _P3,r6
;    blink.c:20: }
    sjmp    00111$
    .area CSEG    (CODE)
    .area CONST   (CODE)
    .area XINIT   (CODE)
    .area CABS    (ABS,CODE)

Code! finally. It looks quite ugly though, using these MOVs and XORs rather than a CPL P3.2 opcode. However, the code in this assembler looks kinda like you'd expect assembly to be looking like - nothing too different than other assemblers.

I also found a single piece of source code for this thing, in the archive of what seems to be a university lab private mailing list archive. It makes a little FM synth called usynth, and seems to be written directly in assembly. This isn't the first place I'd look for information at, but I'll take whatever I can right now. It looks similar to what I already know from looking on SDCC output. Notably this part looks all familiar:

.area INTV (ABS)
.org 0x0000
_int_reset:
	ljmp _start
.org 0x0003
_int_ex0:
	reti
.org 0x000b
_int_t0:
	ljmp T0_ISR
	.ds 5

.area CSEG (ABS,CON)
.org 0x0080

_start:
	clr IE.7

 This is pretty much all I need it seems. Good enough, I guess I know now how to use the assembler now. Lets get going to writing our own code :)

Discussions

Ken Yap wrote 12/13/2019 at 21:02 point

>It looks quite ugly though, using these MOVs and XORs rather than a CPL P3.2 opcode.

To get SDCC to generate the optimal cpl code you have to specify the bit in the port, not the whole port, i.e.

P3_2 ^= 1;

See https://hackaday.io/project/162267/logs

  Are you sure? yes | no

HummusPrince wrote 12/14/2019 at 16:21 point

Thank you for your input, I'll definitely check your tuner project :D

 Though I must say that I'd still expect SDCC to optimize it into a CPL, given a corresponding flag at compilation time.

  Are you sure? yes | no

Ken Yap wrote 12/14/2019 at 19:53 point

It's not that smart. And anyway operating on the bit expresses your intent better, no need to convert the bit position into a mask.

  Are you sure? yes | no