Close

Kestrel-2DX Runs its First C Program

A project log for Kestrel Computer Project

The Kestrel project is all about freedom of computing and the freedom of learning using a completely open hardware and software design.

samuel-a-falvo-iiSamuel A. Falvo II 09/02/2017 at 03:564 Comments

So, I decided to try getting my RISC-V GCC compiler working with my Kestrel-2DX again, and this time, for whatever reason, my mental model just "clicked" and things worked.  It was an iterative process to get this far; however, I'll describe things in the rough order I've accomplished things.

Here's the C program I wanted to run:

/* Invert the contents of the video frame buffer on cold boot. */
unsigned long long *scrp, *endp;
void
_start(void) {
    scrp = (unsigned long long *)0x10000;
    endp = scrp + 2000;
    while(scrp < endp) {
        *scrp ^= 0xFFFFFFFFFFFFFFFF;
        scrp++;
    }
    while(1) ;
}

I was able to compile this into statically-linked ELF binary file with the following command:

./riscv64-unknown-elf-gcc -O1 -c floop.c -o floop.o -march=RV64I
./riscv64-unknown-elf-ld floop.ld floop.o -o floop.exe -nostdlib

You'll notice that I have a custom loader script, which looks like this:

ENTRY(_start)
MEMORY
{
        ROM (rx) : ORIGIN = 0x00000, LENGTH = 0x8000
        RAM (rwx) : ORIGIN = 0x14000, LENGTH = 0x8000
}
SECTIONS {
        .text :
        {
                . = ALIGN(4);
                *(.text)
                *(.text*)
                . = ALIGN(4);
        } >ROM
        .rodata :
        {
                . = ALIGN(8);
                *(.rodata)
                *(.rodata*)
                . = ALIGN(8);
        } >ROM
        .data :
        {
                . = ALIGN(8);
                *(.data);
                *(.data*);
                . = ALIGN(8);
        } >RAM
        .bss :
        {
                . = ALIGN(8);
                *(.bss)
                *(.bss*)
                . = ALIGN(8);
        } >RAM
}

RAM technically starts at address 0x10000; however, the MGIA fetches its video frame from that location, so we configure the linker to place global data variables 16KB away.

Then, to pull out just the code and constant data, I used the following:

./riscv64-unknown-elf-objcopy -j .text -j .rodata floop.exe -O binary floop.bin

At this point, I have a raw binary image.  A problem remains, however.  The constant data precedes the code; thus, I cannot just have the processor reset directly into _start.

$ xxd floop.bin   # note data at offset $00, not $38 or something.
0000000: dec0 ad0b ea1d ad0b 1000 0000 0000 0000  ................
0000010: b707 0100 3747 0100 1307 07e8 83b6 0700  ....7G..........
0000020: 93c6 f6ff 23b0 d700 9387 8700 e398 e7fe  ....#...........
0000030: b747 0100 9387 07e8 3707 0100 2330 f700  .G......7...#0..
0000040: 6f00 0000                                o...

Thus, I need a bootstrap of some kind, and I need to place the C code somewhere away from address 0.

So, I write a simple bootstrap routine in raw assembly to scan ROM for a special "resident" structure (an idea I learned from coding directly on and for AmigaOS); nothing fancy, just something that would let me find the address of _start.

        include "regs.i"
        addi    a0, x0, $200    ; Start at address 512
L0:     auipc   a1, 0
        ld      a1, chkdword-L0(a1)
        lui     a2, $8000
L1:     ld      a3, 0(a0)       ; Did we find the checkword?
        beq     a3, a1, yup
        addi    a0, a0, 8
        blt     a0, a2, L1
        addi    a0, x0, -1      ; Deadlock with all LEDs lit if not found.
        lui     a1, $20000
        sh      a0, 2(a1)
        jal     x0, *
yup:    ld      a3, 8(a0)       ; Get startup procedure's *offset*
        add     a3, a3, a0      ; Get startup procedure's *address*
        lui     sp, $14000      ; Set up C stack pointer.
        jalr    x0, 0(a3)       ; Let there be C.
        align   8
chkdword:       dword   $0BAD1DEA0BADC0DE
        adv     $8000, $CC 

I then altered the C code to include the following at the very start:

struct Resident {
        unsigned long long r_matchWord;
        void (*r_fn)();
};
void _start(void);
const struct Resident R = { 0x0BAD1DEA0BADC0DE, &_start };
static unsigned long long *scrp, *endp;
// ...etc...

After recompiling as above, I now needed to embed the C code into the binary file my personal assembler produced.

dd if=floop.bin of=rom.bin bs=512 seek=1

Whoops, this has the effect of truncating the file; I have to re-pad it to 32KB before making the Verilog module with the ROM's contents.

dd if=/dev/zero of=rom.bin bs=1 count=1 seek=32767

There, I now have a completed 32KB image.  I rebuild the Verilog ROM module:

make rtl/rom.v

 edit the resulting Verilog file because Xilinx's flavor of Verilog is retarded and won't accept the sane syntax that Yosys, Icarus, *AND* Verilator accepts.  One of these days, I'll fix my tooling to automate this.

Anyway, after that was done, I resynthesized the design, and lo and behold, I had a white video display!  That means _start was discovered and dispatched to, and code generated by the C compiler was running!  Woooo!!

Discussions

JL9791 wrote 09/04/2017 at 14:15 point

Woot! Glad it is coming along :)

  Are you sure? yes | no

Samuel A. Falvo II wrote 09/04/2017 at 23:21 point

Me too.  I'm tired of suffering defeat at the hands of fate.  ;)

  Are you sure? yes | no

Ed S wrote 09/03/2017 at 12:40 point

Success! Congratulations.

  Are you sure? yes | no

Samuel A. Falvo II wrote 09/03/2017 at 14:57 point

Thanks!  I've since written text output code to print messages to the screen, and the rudiments of a cursor manipulation package as well.  I think my next step is to integrate the KIA core and get a working PS/2 keyboard again.

  Are you sure? yes | no