FORTH_log_01
- 2025.04.14 23:41:19 Založení souboru
- 2025.04.18 premýšlím jak vytvářet struktury ve FLASH (hlavně teda slova) a zkouším zapojit uměláka, zdá se, že přez C/C++ to moc nepůjde a budu stejně muset použít přímo asembler:
- poznámka - když v asembleru použiju ".extern symbol", tak už nepotřebuju includovat žádné *.h, protože už jsem přece řekl, že "tahle adresa je někde jinde a dostanu ji od linkeru"
- podobně ".global symbol" řiká že tuhle adresu exportuju
- vlastně žádné hlavičky moc nepotřebuju, protože nemám žádné typy a tak a jen potřebuju překladači říct, co je odjinud, ale už mě nezajímá odkud.
- viz "asm hlavičky" níže
Problemy s C/C++
Pisu si FORTH na AVR v C/C++ a potrebuju do FLASH nejak rozumne ulozit struktury s ruznou delkou (WORDs pro FORTH).
budou obsahovat odkazy na jine struktury a odkazy na funkce v C/C++.
V assembleru by to vypadalo asi nejak takhle:
; PLUS w_plus: .word 0 ; next .byte 1, '+' ; name .word f_plus ; address of function f_plus ; DUP w_dup: .word w_plus ; next .byte 3, 'D','U','P' ; name .word f_dup ; address of function f_dup ; DOUBLE w_double: .word w_dup; next .byte 6,'D','O','U','B','L','E' ; name .word f_docol ; address of function DOCOL to interpret next data .word w_dup,w_plus,f_exit ; : DOUBLE DUP + ;
Základní příklad propojení mezi dvěma moduly
ALFA.S ; hlavička .extern ALFA_in ; symbol, na který bude odkazováno z jiného modulu .global ALFA_out ; globální symbol, který bude viditelný pro ostatní moduly ; data v ALFA some_data: .byte 1, 2, 3, 4 .word ALFA_in ; adresa ALFA_in (toto bude propojeno při linkování) .byte 5, 6, 7, 8 ALFA_out: .byte 9, 10, 11 ; data na konci modulu ALFA BETA.S ; hlavička .extern BETA_out ; symbol, na který bude odkazováno z jiného modulu .global BETA_in ; globální symbol pro tento modul ; data v BETA some_other_data: .byte 21, 22, 23, 24 .word BETA_in ; adresa BETA_in BETA_out: .byte 25, 26, 27 ; data na konci modulu BETA
Pokud máte například dva moduly (ALFA.S a BETA.S), přeložte je:
as -o ALFA.o ALFA.S as -o BETA.o BETA.S
A poté je spojte pomocí linkeru:
ld -o demo ALFA.o BETA.o
Linker propojí symboly mezi těmito moduly a při běhu programu budou data správně propojena. You said: kdyz udelam as -o ALFA.o ALFA.S as -o BETA.o BETA.S tak dostanu ALFA.o a BETA.o, kde jeste jsou hodnoty tech ALFA_in a BETA_in nevyresene a budou doplneny az pri linkovani linkerem. Jak rict linkeru, ze do ALFA_in ma vyplnit adresu BETA_out a do BETA_in ma vyplnit nulu? ChatGPT said:
Aby bylo možné při linkování nahradit symboly jako ALFA_in a BETA_in správnými adresami (v tomto případě adresou BETA_out pro ALFA_in a nulou pro BETA_in), můžete využít několik metod, jak linker instruovat, co se má s těmito symboly stát.
Níže uvádím několik způsobů, jak toho dosáhnout: Použití skriptu linkeru (linker script)
Nejlepší způsob, jak to udělat, je použít linker script. Linker script je soubor, který umožňuje linkovacímu procesu specifikovat, jakým způsobem mají být symboly propojeny a jaké hodnoty mají být přiřazeny symbolům. Krok 1: Vytvoření linker skriptu
Vytvoříte si vlastní linker script, který definuje, jak se mají symboly při linkování přiřadit. Tento skript můžete použít při linkování a řídit tak hodnoty symbolů.
Vytvořte soubor, například custom.ld, s následujícím obsahem:
SECTIONS { . = 0x10000; /* Místo, kde začíná kód (např. adresa pro mikrokontrolér) */ .text : { *(.text) /* Všechny kódy (funkce) */ } .data : { *(.data) /* Data, která budou inicializována */ } .bss : { *(.bss) /* Data, která budou nainicializována na nulu */ } /* Určujeme hodnoty pro symboly ALFA_in a BETA_in */ ALFA_in = BETA_out; /* Nastavení ALFA_in na adresu BETA_out */ BETA_in = 0; /* Nastavení BETA_in na 0 */ }
Krok 2: Linkování s linker skriptem
Pak při linkování použijete tento skript takto:
ld -o demo ALFA.o BETA.o -T custom.ld
Tento skript říká linkeru, že symbol ALFA_in by měl být nahrazen adresou BETA_out a symbol BETA_in nulou.
Použití příznaků pro linker
Pokud nechcete používat celý vlastní linker skript, můžete při linkování použít příznaky, které vám umožní nastavit hodnoty symbolů přímo. Krok 1: Použití příznaků pro definici symbolů
Pokud máte pouze pár symbolů, můžete použít příznaky linkeru, které umožní definovat symboly přímo při linkování. Například, použijte -e k určení startovní funkce a -defsym pro definici symbolů.
Příklad linkování:
ld -o demo ALFA.o BETA.o -e main -defsym ALFA_in=BETA_out -defsym BETA_in=0
Tento příkaz linkuje ALFA.o a BETA.o a přiřadí symbolu ALFA_in adresu BETA_out a symbolu BETA_in hodnotu 0. * -e main: Určuje počáteční funkci (v tomto případě main, což je standardní vstupní bod programu). * -defsym ALFA_in=BETA_out: Definuje symbol ALFA_in jako adresu BETA_out. * -defsym BETA_in=0: Nastaví symbol BETA_in na hodnotu 0.
Úvaha o hlavičkách pro asembler
Možná by mělo smysl něco jako při překladu v make ještě každý modul projít a vybrat všechny .global a vytvořit nový soubor (typu ALFA.export) kde by byly jako .extern
A moduly, které chtějí něco z ALFA používat by měly #include "ALFA.export" a neobsahovaly by žádné .export direktivy, takže pokud by použily symbol z ALFA, tak by ten .export měly v tom includovaném souboru a pokud by takový symbol v ALFA nebyl, tak by překladač vyhodil chybu
a v Makefile by pro tohle bylo pravidlo
%.export: %.S grep "^ *.global " $< |sed "s/.global/.extern/" > $@
(a každý *.S by závisel na použitých *.extern)
- Na druhou stranu asi bude lepsi, kdyz pri prekladu budou vsechny .extern pritomne a az pri linkovani se bude testovat, zda je neco naplni.
Pokračování - IP a stack a první slova
- 2025.04.19 Nějak jsem se vymotal ze syntaktických problémů a mám teď zhruba funkční stack, IP a několik prvních slov, vypadá to takhle:
Definice typů
Trochu jsem se rozjel, uvidím, co bude opravdu potřeba:
typedef __memx const uint8_t memxB; // 1B byte somewhere in FLASH or RAM typedef __memx const char memxC; // 1B character somewhere in FLASH or RAM /* * ukazatel na ně může být buď v RAM, nebo "někde", ale musí být 3B, protože ukazuje na "někde" * */ typedef memxB *pB; // 3B pointer, 1B target pointer in RAM to Byte "somewhere" typedef memxC *pC; // 3B pointer, 1B target pointer in RAM to Char "somewhere" typedef void (*pFNC)(void); // 2B pointer in RAM to start of function in FLASH (cannot be in RAM) - call it (*pFNC)() typedef __memx const memxB *xpB; // 3B pointer 1B target pointer "somewhere" to Byte "somewhere" typedef __memx const memxC *xpC; // 3B pointer 1B target pointer "somewhere" to char "somewhere" typedef __memx const void (*xpFNC)(void); // 2B pointer "somewhere" to start of function in FLASH (cannot be in RAM) - call it (*xpFNC)() /* * a ukazatele na 3B ukazatele "někde" na něco mohou opět být buď v RAM, nebo "někde" * */ typedef xpB *pxpB; // 3B pointer in RAM to 3B pointer "somewhere" to Byte "somewhere" typedef xpC *pxpC; // 3B pointer in RAM to 3B pointer "somewhere" to Char "somewhere" typedef xpFNC *pxpFNC; // 3B pointer in RAM to 2B pointer "somewhere" to start of function in FLASH - call it (*(*pxpFNC))() typedef __memx const xpB *xpxpB; // 3B pointer "somewhere" to 3B pointer "somewhere" to Byte "somewhere" typedef __memx const xpC *xpxpC; // 3B pointer "somewhere" to 3B pointer "somewhere" to Char "somewhere" typedef __memx const xpFNC *xpxpFNC; // 3B pointer "somewhere" to 2B pointer "somewhere" to start of function in FLASH - call it (*(*xpxpFNC))() /* * and now we can have header (two parts as we do not know len of name): * */ typedef struct head1 { // {{{ pB next; // 3B: pointer to next header "somewhere" uint8_t len; // 1B: up to 31 (=5bits), also some flags there (7+6bit) const char name[]; // len B:name of WORD } head1; // }}} typedef __memx const head1 *xpHead1; typedef struct head2 { // {{{ pFNC codepoint; // 2B: pointer to function to interpret data xpxpFNC data[]; // 3B: pointer to 2B pointer "somewhere" to function to interpret data - pointer to head2 "somewhere" } head2; // }}} typedef __memx const head2 *xpHead2; // 3B pointer to head2 "somewhere" // head2.data is array of xpHead2 typedef xpxpFNC *pxpxpFNC; // 3B pointer in ram to 3B pointer "somewhere" to 2B pointer "somewhere" to start of function in FLASH - call it (*(*xpxpFNC) typedef __memx const xpxpFNC *xpxpxpFNC; /* * and now we can have Instruction Pointer to data in head2 "somewhere" : * */ xpxpxpFNC IP; extern xpC top_head; // pointer to last header in asm module
NEXT
// #define NEXT (*(*(*IP++)))() #define NEXT next() void next(){(*(*(*IP++)))();}
Sice to nejsou dvě instrukce jako na 86_64, ale dělá to to samé. GCC je natolik chytrý, že volání funkce těsně před koncem změní na skok ( Tail call) a tak se NEXT nevolá, ale skočí se na něj a stejně tak se skočí tam, kam ukazuje zprostředkovaně IP (na codeword aktuálního slova).
datový stack
zatím takhle:
uint16_t *stack; uint16_t pop() {return *(stack++);} void push(uint16_t x) { *(--stack)=x;} uint16_t peek(){ return *(stack);}
a 100 prvků stacku by mělo být dost pro každého :)
uint16_t stck[100]; void setup(){ stack=&stck[99]; // last address in stck stack++; // now we can push something and have it at top of stck
funkce pro primitiva
void f_dup(){ push(peek()); NEXT; } void f_plus(){ push(pop()+pop()); NEXT; }
hlavičky slov
Protože napsat to v Céčku by byl strašný opruz, tak je to prostě asembler
.section .text .global top_head .global here here: .macro FLASH_PTR addr ; {{{ .word \addr .byte 0x80 .endm ; }}} .macro DEFWORD lbl,next, attr, name, codeword ; {{{ \lbl\(): FLASH_PTR \next .byte len\@ + \attr name_start_\@: .ascii "\name" ; name without \0 .equ len\@,(. - name_start_\@) \lbl\()_cw: .extern \codeword .word \codeword ; 2B address of function in FLASH .endm ; }}} DEFWORD w_plus,0,0, "+", f_plus DEFWORD w_dup, w_plus, 0, "DUP", f_dup DEFWORD w_double, w_dup, 0, "DOUBLE", f_docol FLASH_PTR w_dup_cw FLASH_PTR w_plus_cw FLASH_PTR f_exit .EQU top_head, w_double