mov a, lb@fp ; read old frame pointer
pushaf ; save
mov a, sp ; read stack pointer
mov fp, a ; set new frame pointer
add A, 8 ; allocate stack frame
mov SP, A ; update stack pointer
...main code here...
mov a, fp ; read current frame pointer
mov sp, a ; retrack over frame
popaf ; load old frame pointer
mov fp, a ; reload old frame pointer
ret
mov a, 4 ; frame offset
add a, fp ; add frame pointer
mov lb@ptr, a
idxm a, ptr
; index in A, returns value in A
load_value_from_table:
pcadd a ; jump to pc + a
ret 42
ret 99
ret -6
ret 127
...etc...
Re the other flags: the flags byte is at IO address 0, so you can test any individual but with t1sn IO.3 or similar. I don't think we can get away with ignoring them...
Seems like we've been duplicating some work.
Cowgol doesn't use stack frames, though, so it's golden.
Hi Guys,
Just found this thread. Seems like we've been duplicating some work.
I was planning to develop a PC based simulator to allow code to be quickly
tested and debugged, and also perhaps an STM32 based one which would allow
code to be tested "in circuit" with downloadability and decent debugging
capability. Would also like to eventually include device programming in
the STM32 but I've not looked really looked into it yet.
Regards,
Dave
FLAG IO_RW 0x00
OV IO_RW FLAG.3
AC IO_RW FLAG.2
CF IO_RW FLAG.1
ZF IO_RW FLAG.0
T1SN OV
GOTO OVCLEAR
NOP
NOP
OVCLEAR:
Your small language seems really interesting. I also do not see a lot of sense in porting "full" C for this architecture, IMHO it's just less of an hassle to write assembly. A mixed language with assembly+some high level constructs is the best thing, which is in fact what PADAUK did with their Mini-C. If we make an open source toolchain it wouldn't be bad to have our own language for this thing. Of course the self-hosting capability of your compiler (while being really cool) here is pointless, but I see myself liking a language like Cowgol with the possibility to interleave assembly code like PADAUK's Mini-C lets you do. I mean, instead of replicating their Mini-C we could take the opportunity to try something else.
Of course if someone manages to make C compile for this MCUs through SDCC or whatever, that would be really cool, but I would like to see how much overhead it is added, especially regarding function calls.
SDCC makes functions non-reentrant by default (i.e. unless the function is marked __reentrant or the --stack-auto command-line argument is supplied), placing local variables at fixed memory locations.
Anyhow my idea for a "universal" instruction table was to have a list of objects like this, if this can somehow interest you:
It may be faster than your current approach (which I like anyway, it is very readable IMHO) but by pattern matching like that may not be the most efficient thing, also you'll have to reconstruct the parameter values and other stuff which would be easier with a table like mine, at least that's what I anticipate.
I'm an engineer and thus obligated to be as lazy as possible
static const auto PMS154_INSTRUCTIONS_SPEC = std::vector<instr_spec_t>
{
// {mnemonic, opcode, opcode_mask, {args...}}
{"nop", 0x0000, 0x3FFF, {}},
{"addc", 0x0060, 0x3FFF, {{Accumulator, 0, 0}}},
{"subc", 0x0061, 0x3FFF, {{Accumulator, 0, 0}}},
{"izsn", 0x0062, 0x3FFF, {{Accumulator, 0, 0}}},
{"izsn", 0x0063, 0x3FFF, {{Accumulator, 0, 0}}},
// ...
{"wdreset", 0x0070, 0x3FFF, {}},
{"pushaf", 0x0072, 0x3FFF, {}},
{"popaf", 0x0073, 0x3FFF, {}},
// ...
{"xor", 0x00C0, 0x3FC0, {{IO, 0, 6}, {Accumulator, 0, 0}}},
{"mov", 0x0180, 0x3FC0, {{IO, 0, 6}, {Accumulator, 0, 0}}},
{"mov", 0x01C0, 0x3FC0, {{Accumulator, 0, 0}, {IO, 0, 6}}},
{"ret", 0x0200, 0x3F00, {{Immediate, 0, 8}}},
{"stt16", 0x0300, 0x3F81, {{Memory, 0, 7}}},
{"ldt16", 0x0301, 0x3F81, {{Memory, 0, 7}}},
{"idxm", 0x0380, 0x3F81, {{Memory, 0, 7}, {Accumulator, 0, 0}}},
{"idxm", 0x0381, 0x3F81, {{Accumulator, 0, 0}, {Memory, 0, 7}}},
{"swapc", 0x0400, 0x3E00, {{IO, 0, 6}, {Bit_N, 6, 3}}},
{"comp", 0x0600, 0x3F80, {{Accumulator, 0, 0}, {Memory, 0, 7}}},
{"comp", 0x0680, 0x3F80, {{Memory, 0, 7}, {Accumulator, 0, 0}}},
{"nadd", 0x0700, 0x3F80, {{Accumulator, 0, 0}, {Memory, 0, 7}}},
{"nadd", 0x0780, 0x3F80, {{Memory, 0, 7}, {Accumulator, 0, 0}}},
// ...
};
Are you the ImageDisk Dave? Interesting to see you pop up on this thread and looking at these low cost microcontrollers.
#include "extern.h"
void func(void)
{
byte x = 5;
byte y = 6;
byte c;
c = x + y - ~x;
}
void FPPA0 (void)
{
.ADJUST_IC SYSCLK=IHRC/2 // SYSCLK=IHRC/2
func();
byte c = 0;
while (1)
{
c++;
}
}
call _SYS(ADR.ROLL);
call _SYS(ADR.ROLL)+1;
call _SYS(ADR.ROLL)+2;
uint8_t eF = CPUioGet(0x00); //get flags from emulated IO port, mapped like in IO flags: (- - - - V A C Z)
uint8_t eSP = CPUioGet(0x02); //get SP from emulated IO port
uint16_t opcode = CPUcodeGet( ePC++ ); //fetch next opcode and advance PC
eCurrentCycle++; //increment current cycle counter
if( 0x3FFE == opcode ) //special opcode ?
{
//TODO... find out what it does. IDE inserts when using ADJUST_CHIP
}
else
//14 bit opcodes 0x0000 - 0x00BF
if( opcode<=0x00BF )
{
switch( opcode )
{
case 0x0000: break; //NOP
...
}
...
else
//6 bit opcodes 0x02.. , 0x2800 - 0x2FFF
if( (0x0200 == (opcode&0x3F00)) || (0x2800 == (opcode&0x3800)) )
{
switch( opcode & 0x3F00 )
{
case 0x0200: eA = opcode&0xFF; ePC=(((uint16_t)CPUmemGet(--eSP))<<8); ePC|=CPUmemGet(--eSP); break; //RET k
case 0x2800: eA += opcode&0xFF; eF=(eA>255)<<1;eA&=0xFF;eF|=!eA; break; //ADD A,k //TODO: OV, AC
case 0x2900: eA -= opcode&0xFF; eF=(eA>=0)<<1;eA&=0xFF;eF|=!eA; break; //SUB A,k //TODO: OV, AC
case 0x2A00: //CEQSN A,k
case 0x2B00: //CNEQSN A,k
T = eA-(opcode&0xFF); //TODO: A-k or k-A ?
if( ((0x2A00==(opcode&0x3F00)) && !T) ||
((0x2B00==(opcode&0x3F00)) && T) )
{
ePC++; eCurrentCycle++;
}
eF=(T>255)<<1;T&=0xFF;eF|=!T; //TODO: OV,AC (based on T)
break;
case 0x2C00: eA &= opcode&0xFF; eF&=~1;eF|=!eA; break; //AND A,k
case 0x2D00: eA |= opcode&0xFF; eF&=~1;eF|=!eA; break; //OR A,k
case 0x2E00: eA ^= opcode&0xFF; eF&=~1;eF|=!eA; break; //XOR A,k
case 0x2F00: eA = opcode&0xFF; break; //MOV A,k
}
}
else
//5 bit opcodes 0x0400 - 0x0500, 0x1800 - 0x27FF
if( (0x0400 == (opcode&0x3E00)) || ((opcode>=0x1800) && (opcode<=0x27FF)) )
{
uint8_t bit = 1<<((opcode>>6)&7);
uint8_t addr = opcode&0x3F;
switch( opcode & 0x3E00 )
{
case 0x0400: T=CPUioGet(addr);CPUioPut(addr,eF&2?T|bit:T&~bit); eF&=~2;eF|=(T&bit)?2:0; break; //SWAPC IO.n
case 0x1800: if( !(CPUioGet(addr)&bit) ) { ePC++; eCurrentCycle++; } break; //T0SN IO.n
case 0x1A00: if( CPUioGet(addr)&bit ) { ePC++; eCurrentCycle++; } break; //T1SN IO.n
case 0x1C00: CPUioPut(addr,CPUioGet(addr)&~bit); break; //SET0 IO.n
case 0x1E00: CPUioPut(addr,CPUioGet(addr)|bit); break; //SET1 IO.n
case 0x2000: if( !(CPUmemGet(addr)&bit) ) { ePC++; eCurrentCycle++; } break; //T0SN M.n
case 0x2200: if( CPUmemGet(addr)&bit ) { ePC++; eCurrentCycle++; } break; //T1SN M.n
case 0x2400: CPUmemPut(addr,CPUmemGet(addr)&~bit);break; //SET0 M.n
case 0x2600: CPUmemPut(addr,CPUmemGet(addr)|bit);break; //SET1 M.n
}
}
else
//3 bit opcodes 0x3000 - 0x3FFF
if( (0x3000 == (opcode&0x3000)) )
{
if( opcode & 0x0800 ) //CALL needs to put current PC on stack
{
CPUmemPut( eSP++, ePC & 0xFF ); //TODO: check if on stack is little endian
CPUmemPut( eSP++, ePC>>8 ); //TODO: check if on stack is little endian
}
eCurrentCycle++;
ePC = opcode & 0x07FF;
}
else
{
//unknown instruction
CPUexceptionEmulation("Unknown instruction", opcode );
}
CPUioPut(0x02,eSP); //store SP to emulated IO port
CPUioPut(0x00,eF); //store flags to emulated IO port
Is there any possibility of there being an internal ROM with more code in it? That would explain the weird jumps and calls.
Do any of these things have SPI or I2C? Because if we ever figure out the programming protocol, and you could make one of these processors program another, you could incredibly cheaply build a hypercube cluster of the flash versions of these things, all bootstrapped from one processor at the corner attached to the outside world... it'd be useless, but fascinating. Sadly, without some sort of fast comms it's probably not worth it.
Perhaps more relevant to these discussions is my past-life of creating development tools for little processors (mostly 8-bit). C compilers, assemblers, debuggers, simulators etc.
[…]
** Mostly retired now, but always up for an interesting project!
Padauk is not making any money selling the programmer, so why don't ask them for the programming protocol?The have been asked and they refused to provide any information.
Would you be interested in contributing to SDCC, a free C compiler targeting small devices?
Wait, are you the Dunfield C Dunfield?
0x0000: 0x0070 WDRESET ;reset watchdog
0x0001: 0x2f00 MOV A, 0x00
0x0002: 0x0182 MOV IO(0x02), A ;SP ;set SP to memory start (0)
0x0003: 0x3fed CALL 0x7ED ;get IHRCR (value inserted from WRITER)
0x0004: 0x018b MOV IO(0x0B), A ;IHRCR ;setup IHRCR
0x0005: 0x3fee CALL 0x7EE
0x0006: 0x019a MOV IO(0x1A), A ;BGTR? (not in datasheet)
0x0007: 0x2f00 MOV A, 0x00 ;setup low voltage detector (value inserted from IDE .CHIP)
0x0008: 0x019b MOV IO(0x1B), A ;MISC_LVR 4V/3V5/3V/2V75/2V5/1V8/2V2/2V
0x0009: 0x2f34 MOV A, 0x34
0x000a: 0x0183 MOV IO(0x03), A ;CLKMD ;setup clock mode (value inserted from IDE .ADJUST_IC)
0x000b: 0x3ffe CALL 0x7FE ;get stored calibration value (stored during programing of OTP)
;This is a nice trick. The OTP reads all as '1' when not programmed. Programing will change the relevant bits to '0'.
;The trick is that you can program the OTP multiple times. It is always possible to change '1' to '0' but never the other way around.
;So they store 0x02FF in code memory at position 0x7FE which translates to RET 0xFF. Later programer can change the 0xFF return value by
;overwriting it with the final value (0xFF still all bits '1').
;This means all the code follows is used for calibration during programing only (big waste of OTP)
0x000c: 0x2aff CEQSN A, 0xFF ;Check if is 0xFF (nothing written there?)
0x000d: 0x3054 GOTO 0x054 ;Jump over calibration routine to user program
0x000e: 0x3fed CALL 0x7ED ;get IHRCR (value inserted from programmer)
0x000f: 0x0b81 MOV [0x01], A ;store it in memory @0x01
0x0010: 0x1f91 SET1 IO(0x11).6 ;PAC.6 ;configure PA.6 as output
;calibration routine
0x0011: 0x2f20 MOV A, 0x20
0x0012: 0x0b80 MOV [0x00], A ;store 0x20 in memory @0x00
0x0013: 0x1ad0 T1SN IO(0x10).3 ;PA.3 ;check for HIGH signal at PA.3 <-- check for handshake signal for WRITER
0x0014: 0x3013 GOTO 0x013 ;wait until PA.3 is high
0x0015: 0x1f90 SET1 IO(0x10).6 ;PA.6 ;set PA.6 to HIGH <-- send response to WRITER
0x0016: 0x0063 DZSN A ;1c big delay, underflows after first loop
0x0017: 0x3016 GOTO 0x016 ;2c inner loop apx. 3*256 cycles = 768 cycles
0x0018: 0x1180 DZSN [0x00] ;1c
0x0019: 0x3016 GOTO 0x016 ;2c outer loop apx. 32*(768+3) = 24672 cycles,
;-224 cycles from first inner loop (was init with 32 instead of 255) = 24448 cycles total delay
0x001a: 0x1d90 SET0 IO(0x10).6 ;PA.6 ;set PA.6 to LOW <-- stop sending response to WRITER
0x001b: 0x18d0 T0SN IO(0x10).3 ;PA.3 ;check for LOW signal at PA.3 <-- wait for WRITER to stop sending handshake signal
0x001c: 0x301b GOTO 0x01B ;wait until PA.3 is low
0x001d: 0x2f01 MOV A, 0x01
0x001e: 0x1950 T0SN IO(0x10).5 ;PA.5 ;if PA.5 is LOW ==> A=0x01 , HIGH ==> A=0xFF <-- WRITER sets ? ? ? value to 1/255 by setting PA.5
0x001f: 0x2fff MOV A, 0xFF
0x0020: 0x0c01 ADD A, [0x01] ;add to IHRCR (value inserted from programmer) 1 or 255 (see above)
0x0021: 0x018b MOV IO(0x0B), A ;IHRCR ;set new IHRCR
0x0022: 0x0b81 MOV [0x01], A ;store new IHRCR value in memory @0x01
0x0023: 0x1ad0 T1SN IO(0x10).3 ;PA.3 ;check for HIGH signal at PA.3 <-- check for handshake signal for WRITER
0x0024: 0x3023 GOTO 0x023 ;wait until PA.3 is high
0x0025: 0x1b50 T1SN IO(0x10).5 ;PA.5 ;check for HIGH signal at PA.5 <-- WRITER signals all done?
0x0026: 0x304f GOTO 0x04F ;jump to end of calibration code
0x0027: 0x2f04 MOV A, 0x04
0x0028: 0x0188 MOV IO(0x08), A ;MISC ;disable low voltage detector
0x0029: 0x18d0 T0SN IO(0x10).3 ;PA.3 ;check for LOW signal at PA.3 <-- wait for WRITER to stop sending handshake signal
0x002a: 0x3029 GOTO 0x029 ;wait until PA.3 is low
;start measurment
0x002b: 0x2f02 MOV A, 0x02
0x002c: 0x0182 MOV IO(0x02), A ;SP ;setup SP to memory @0x02
0x002d: 0x1304 CLEAR [0x04] ;zero memory @0x04
0x002e: 0x1305 CLEAR [0x05] ;zero memory @0x05
0x002f: 0x2f55 MOV A, 0x55
0x0030: 0x0b82 MOV [0x02], A ;store 0x55 in memory @0x02
0x0031: 0x2f00 MOV A, 0x00
0x0032: 0x0b83 MOV [0x03], A ;store 0x00 in memory @0x03
;16 bit loop (0x0055) times some operations with internal value LDSPTL:LDSPTH (timing register?)
0x0033: 0x0006 LDSPTL ;?load from timing register L
0x0034: 0x0b04 XOR [0x04], A
0x0035: 0x0007 LDSPTH ;?load from timing register H
0x0036: 0x0805 ADD [0x05], A
0x0037: 0x1584 SL [0x04] ;rotate left 16 bit value
0x0038: 0x1685 SLC [0x05]
0x0039: 0x1004 ADDC [0x04]
0x003a: 0x1282 DECM [0x02] ;memory low byte -1
0x003b: 0x1083 SUBC [0x03] ;memory high byte -1 if carry set
0x003c: 0x1a40 T1SN IO(0x00).1 ;FLAG.CF ;test for carry (0x0000 -1 => carry set)
0x003d: 0x3033 GOTO 0x033 ;loop
0x003e: 0x1f90 SET1 IO(0x10).6 ;PA.6 ;set PA.6 to HIGH <-- send response to WRITER (measurement finished)
;send bit by bit the 16 bit measured value
0x003f: 0x1ad0 T1SN IO(0x10).3 ;PA.3 ;check for HIGH signal at PA.3 <-- check for handshake signal for WRITER
0x0040: 0x303f GOTO 0x03F ;wait until PA.3 is high
0x0041: 0x1584 SL [0x04] ;16 bit shift left
0x0042: 0x1685 SLC [0x05]
0x0043: 0x0590 SWAPC IO(0x10).6 ;PA.6 ;set PA.6 according to carry (highest bit from 16 bit value before shift)
0x0044: 0x18d0 T0SN IO(0x10).3 ;PA.3 ;check for LOW signal at PA.3 <-- wait for WRITER to stop sending handshake signal
0x0045: 0x3044 GOTO 0x044 ;wait until PA.3 is low
0x0046: 0x1950 T0SN IO(0x10).5 ;PA.5 ;check if PA.5 is LOW <-- WRITER signals to send next bit
0x0047: 0x303f GOTO 0x03F
0x0048: 0x1d90 SET0 IO(0x10).6 ;PA.6 ;set PA.6 to LOW
0x0049: 0x1ad0 T1SN IO(0x10).3 ;PA.3 ;check for HIGH signal at PA.3 <-- check for handshake signal for WRITER
0x004a: 0x3049 GOTO 0x049 ;wait until PA.3 is high
0x004b: 0x18d0 T0SN IO(0x10).3 ;PA.3 ;check for LOW signal at PA.3 <-- wait for WRITER to stop sending handshake signal
0x004c: 0x304b GOTO 0x04B ;wait until PA.3 is low
0x004d: 0x1b50 T1SN IO(0x10).5 ;PA.5 ;check for HIGH signal at PA.5 <-- WRITER signals to redo measurement
0x004e: 0x302b GOTO 0x02B ;if PA.5 is LOW do the measurement again
;here seems to be an error in the program from PADAUK, very unlikely PA.3 went hight during last 2 instructions, should be HIGH check for sure
0x004f: 0x18d0 T0SN IO(0x10).3 ;PA.3 ;check for LOW signal at PA.3
0x0050: 0x304f GOTO 0x04F ;wait until PA.3 is low
0x0051: 0x1b50 T1SN IO(0x10).5 ;PA.5 ;check for HIGH signal at PA.5 <-- WRITER signals all done?
0x0052: 0x3011 GOTO 0x011 ;if PA.5 is LOW do the complete calibration again (including all handshakes)
0x0053: 0x3053 GOTO 0x053 ;calibration successful, endless loop (chip needs reset + writing of calibration value from WRITER)
;----------------------------------------------------------------------------------------------------------------------------------------------
0x0054: 0x018b MOV IO(0x0B), A ;IHRCR ;normal program start, set IHRCR
0x0055: 0x3055 GOTO 0x055 ;the user program (was just a "while(1){}" loop)