Found 2 more instructions which are not in manual:
LDSPTL 0x0006
LDSPTH 0x0007
They are fixed instructions with no arguments.
Speculation...
Looks like it is a syntax which uses L and H
Since the stack pointer is mapped on IO 0x02 and does not have High or Low, the "SP" inside the name might me misleading
----
There are 2 more undocumented instructions which the IDE knows but refuses to compile. I tried many many different 1xx processors:
WORD data
...
LDTABL data
LDTABH data
Compiler always says: "The 'LDTABx' not be supported at PFS154" (for every 1xx processor I tried)
All other instructions from the list are already mapped.
One special exception is the "TRAP" instruction. The IDE just put's a "NOP" inside the binary. I assume this is used for the ICE only... so need to wait for my ICE to arrive
@ js_12345678_55AA: Could you post the modifications you made to deobfuscate the PDKs in the USB_Driver directory?
I updated the initial post (both source and windows binary)
Speaking of ICE
The USB_Driver/USB directory also seems to contain the firmware for the ICE (*.usb and *.hid files)
As far as I can tell this should be firmware for an Cypress EZ-USB FX2LP Chip CY7C68013A (8051 code) it is not obfuscated.
I assume that the ICE*PDK files are for GAL/PAL/FPGA device seen in the video of the ICE. (Cypress even markets the chip as an FPGA companion "Booting an FPGA from FX2LP" so probably an FPGA.
AN63620 - Configuring a Xilinx Spartan-3E FPGA Over USB Using EZ-USB FX2LP™:
"This application note demonstrates a technique for dynamically configuring a Xilinx Spartan-3E Field Programmable Gate Array (FPGA) over USB using EZ-USB FX2LP, a high-speed USB peripheral controller. After the FPGA is configured, FX2LP can act as a high-speed data path between the USB host and the FPGA. This capability of FX2LP enhances
FPGA-based USB applications such as logical analyzers, oscilloscopes, image processing,
and high-speed data acquisition."
They are all wildly different from each other, which would make sense if they all are for differnt cores.
The file sizes are however all divisible by 64 -> Logic cells/blocks?
ICE_3.usb and WRITER_5.usb on the other hand are almost identical, so I assume thats for the same thing (as far as I understand thats the multicore ICE)
ICE5.fw would then be the obfuscated single core firmware for the ICE that Dave has. It seems that they still use a Cypress FX2 chip, so probably almost identical to ICE_3.usb.
Maybe that helps with deobfuscation.
@ js_12345678_55AA:
Also: in the USB_Driver/USB directory there seems to be the firmware for the programmer (in obfuscated form):
P5SP_BOOT_UPDATER.fw
P5SP_G1.fw
P5SP_G2.fw
P5SP_TESTER.fw
from Dave's video the P5SP files could be for an STM32F072V8T6
You asked for it... here it is:
It's a command line utility called
defw which requires 2 parameters: inputfile and outputfile.
Example:
./defw input.fw output.binCompilation with
gcc -o defw defw.cOr use the attached compiled executable for Windows.
Have fun,
JS
P.S. works for all *.fw files and also the *.hid files in "USB_Driver/USB" directory. All other files (*.usb) there seem not to be obfuscated. For the PDK files use "depdk".
defw.c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
int main( int argc, const char * argv [] )
{
if( 3 != argc ) {
printf("usage: %s inputfile outputfile\n\n", argv[0]);
return 0;
}
FILE* fin = fopen( argv[1], "rb");
if( !fin ) { printf("Could not open %s for reading.\n", argv[1]); return -1; }
FILE* fout = fopen( argv[2], "wb");
if( !fin ) { printf("Could not open %s for writing.\n", argv[2]); return -1; }
uint16_t data[0x10000];
int lenwords = fread( data, sizeof(uint16_t), sizeof(data)/sizeof(uint16_t), fin );
if( lenwords<=0 ) {
printf("Could not read %s.\n", argv[1]); return -1;
}
for( uint32_t i=0; i<lenwords; i++ ) {
data[ i ] ^= i;
if( i & 1 ) data[ i ] ^= 0x55;
else
if( i & 2 ) data[ i ] ^= 0xAA23;
else
if( i & 4 ) data[ i ] ^= 0x3759;
else
data[ i ] ^= 0xAFCD;
data[ i ] ^= (lenwords-i-1)<<3;
}
if( lenwords != fwrite( data, sizeof(uint16_t), lenwords, fout ) ) {
printf("Error writing output file\n");
return -1;
}
fclose(fin);
fclose(fout);
return 0;
}
... it is indeed STM32 firmware in there :-)
Edit: fixed forum formatting of data[ i ]
Hi,
seems youtuber "bigclivedotcom" spotted a PADAUK MCU containing device in the wild:
small error:
data ^= ...
should read
data square brackets i ^= ... (Forum doesnt let me post square brackets
)
fixed locally and seems to work
and more voltages appear:
T:CN5/CN6
VPP_PWR != 11V
T:CN7
T:CN12/CN6/CN5
VPP_PWR != 0V
VDD_PWR != 7.8V
T:CN11
T:CN11/CN4/CN3
VDD_PWR != 0V
HVPP_IC != 11V
T:IC_PA5
T:Q32
VPP_IC != 0V
VDD_IC != 7.8V
T:IC_VDD
T:Q9
VDD_IC != 0V
`pGp
d!/H
VDD_PWR != 3V
T:CN11
seems youtuber "bigclivedotcom" spotted a PADAUK MCU containing device in the wild:
Too bad he doesnt desolder the chip to show the bottom so we can see the package markings.
Just FYI package markings on the chips I have:
PMS150C: YS17 RBAAADO 1839XA0
PMC232-S16A: FS20 FC62450 1737YAD
PFS154-S16: YP56 R2E9691 1609XBD
Bj8P509FNB (supposedly clone of elan 13-bit clone of microchip 12c509): BJY180338
I was thinking about creating a library (may be called "libpdk") which contains all the code one may need to create tools for these MCUs. The lib would contain stuff like the PDK decryption/encryption code, the instruction list with their opcode encoding/decoding rules, etc... then all the tools like disassembler, assembler, emulator, etc... can link to the same library. This would let us have only one codebase where the "important stuff" is so if we need to make changes and adjustments (like adding new instrs etc) we wouldn't have to change every single other tool. I'm starting to layout the instruction list in C++ in a way that can be used both to assemble, disassemble or emulate stuff. Is there a place for people interested in helping with the code to chat with the others so we somehow coordinate the work? Like a Slack or Discord channel or something
Too bad he doesnt desolder the chip to show the bottom so we can see the package markings.
You asked for it...
Just stopped at ALDI and saw them having a similar LED Christmas lights offering for 2.99 EUR.
I bought and opened it (without powering it on, because "Don't turn it on, take it apart!"
) ...
... and yes, PCB and MCU looks same as from bigclivedotcom's video.
But to my surprise it is having a 32kHz crystal so it can do much better than the more cost optimized version bigclivedotcom was testing.
Last picture shows the MCU from back side. I'm pretty sure it is a PMC154 (just the cheapest and the pinout for GND, VDD, crystal also matches).
JS
Last picture shows the MCU from back side. I'm pretty sure it is a PMC154 (just the cheapest and the pinout for GND, VDD, crystal also matches).
I dont think so. The chips I have have 3 lines of markings. This just has the one.
No Bojuxing Industry BJ8P509FNB either because thats more laser carved and the chip marking you have seems inkjetted.
All these chips are probably very interchangable anyhow. Or they just rewrite the code, after all these are throw away products
Maybe ALDI has the premium version for germans
thus the crystal for milisecond switching precision
(seriously the cheapest crystal I could find on lcsc costs more than the pms150c
utter madness)
This isnt complete but its a start.
If you would like to extend this, youre welcome
Its based on
https://github.com/limkokhole/radare2/wiki/Implementing-a-new-architectureand by no means any good, but I think its not too hard to get it usable.
create a directory. put the two files in it. install radare2 if you havent already.
make
sudo make install
I also had to
cp /usr/lib/radare2/last/* ~/.config/radare2/plugins
otherwise the plugin isnt recognized
rasm2 -L should list it at the end
and then you can
rasm2 -a PADAUK -d 0x0069
to get: neg a
padauk.c
/* Padauk disassembler plugin for radare */
#include <r_asm.h>
#include <r_lib.h>
#include <string.h>
static struct {
ut8 op1;
ut8 op2;
char *name;
} ops[] = {
{0x00, 0x00, "nopi"},
{0x00, 0x60, "addc a"},
{0x00, 0x61, "subc a"},
{0x00, 0x62, "izsn a"},
{0x00, 0x63, "dzsn a"},
{0x00, 0x64, "?"},
{0x00, 0x65, "?"},
{0x00, 0x66, "?"},
{0x00, 0x67, "pcadd a"},
{0x00, 0x68, "not a"},
{0x00, 0x69, "neg a"},
{0x00, 0x6a, "sr a"},
{0x00, 0x6b, "sl a"},
{0x00, 0x6c, "src a"},
{0x00, 0x6d, "slc a"},
{0x00, 0x6e, "swap a"},
{0x00, 0x6f, "?"},
{0x3f, 0xff, NULL}};
static int _PADAUKDisass (RAsm *a, RAsmOp *op, const ut8 *buf, int len) {
int i;
op->size = 2;
for (i=0; ops[i].name != NULL; i++) {
if (ops[i].op1 == buf[0]) {
if (ops[i].op2 == buf[1]) {
sprintf (op->buf_asm, "%s", ops[i].name);
break;
}
}
}
return op->size;
}
RAsmPlugin r_asm_plugin_padauk = {
.name = "PADAUK",
.arch = "PADAUK",
.license = "LGPL3",
.bits = 16,
.desc = "Padauk disassembler",
.disassemble = &_PADAUKDisass,
};
#ifndef CORELIB
struct r_lib_struct_t radare_plugin = {
.type = R_LIB_TYPE_ASM,
.data = &r_asm_plugin_padauk
};
#endif
Makefile
NAME=padauk
R2_PLUGIN_PATH=$(shell r2 -hh|grep LIBR_PLUGINS|awk '{print $$2}')
CFLAGS=-g -fPIC $(shell pkg-config --cflags r_asm)
LDFLAGS=-shared $(shell pkg-config --libs r_asm)
OBJS=$(NAME).o
SO_EXT=$(shell uname|grep -q Darwin && echo dylib || echo so)
LIB=$(NAME).$(SO_EXT)
all: $(LIB)
clean:
rm -f $(LIB) $(OBJS)
$(LIB): $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $(LIB)
install:
cp -f $(NAME).$(SO_EXT) $(R2_PLUGIN_PATH)
uninstall:
rm -f $(R2_PLUGIN_PATH)/$(NAME).$(SO_EXT)
This is a slightly improved version tested with radare 3.1 git.
The documentation for radare is shall we say "sparse" and every backend seems to have multiple layers of indirection and slightly different ways of going about it, especially for the assembler.
But once you found a way it works quite nicely. This is also not very complete and thus minimalistic because I dont know what the best way to de/encode the instructions is.
The table approach has the advantage of being inversible i.e the dissassembler is the inverse of the assembler, but its quite pointless to do it in this manor for instructions that take addresses etc.
Use the makefile from above.
make && sudo make install
and then you can do
rasm2 -a padauk -d 0x0073
to get "popaf" or
rasm2 -a padauk 'addc a'
to get "0x0060"
and you also get stuff like this
rasm2 -a padauk 'nop; ret; reti; addc a; sr a;'
rasm2 -a padauk -d 0x0000006000720073
for free
padauk.c
/* Padauk (dis)assembler plugin for radare 3.1 git - DocBen 2018 */
#include <r_asm.h>
#include <r_lib.h>
#include <string.h>
static struct {
ut16 op;
char *name;
} ops[] = {
{0x0000, "nop"},
{0x0060, "addc a"},
{0x0061, "subc a"},
{0x0062, "izsn a"},
{0x0063, "dzsn a"},
{0x0064, "?"},
{0x0065, "?"},
{0x0066, "?"},
{0x0067, "pcadd a"},
{0x0068, "not a"},
{0x0069, "neg a"},
{0x006c, "src a"},
{0x006d, "slc a"},
{0x006a, "sr a"},
{0x006b, "sl a"},
{0x006e, "swap a"},
{0x006f, "?"},
{0x0070, "wdreset"},
{0x0071, "?"},
{0x0072, "pushaf"},
{0x0073, "popaf"},
{0x0074, "?"},
{0x0075, "reset"},
{0x0076, "stopsys"},
{0x0077, "stopexe"},
{0x0078, "engint"},
{0x0079, "disgint"},
{0x007b, "reti"},
{0x007a, "ret"},
{0x007c, "mul"},
{0x007d, "?"},
{0x007e, "?"},
{0x007f, "?"},
{0x4000, NULL}};
int _PADAUKDisass (RAsm *a, RAsmOp *op, const ut8 *buf, int len) {
int i;
op->size = 2;
const char *buf_asm = "unknown";
for (i=0; ops[i].name != NULL; i++) {
if (ops[i].op == 0x100 * buf[0] + buf[1]) {
buf_asm = sdb_fmt ("%s", ops[i].name);
break;
}
}
r_strbuf_set (&op->buf_asm, buf_asm);
return op->size;
}
int _PADAUKAss (RAsm *a, RAsmOp *op, const char *buf) {
int i;
op->size = 2;
ut16 opbuf = 0x4000;
const char *buf_hex = "unknown";
for (i = 0; ops[i].name != NULL; i++) {
if (!strncmp(ops[i].name, buf, strlen(ops[i].name))) {
opbuf = ops[i].op;
buf_hex = sdb_fmt ("0x%.4X\n", opbuf);
break;
}
}
r_strbuf_set (&op->buf_hex, buf_hex);
return op->size;
}
RAsmPlugin r_asm_plugin_padauk = {
.name = "padauk",
.arch = "padauk",
.license = "LGPL3",
.bits = 16,
.desc = "Padauk (dis)assembler",
.disassemble = &_PADAUKDisass,
.assemble = &_PADAUKAss,
};
#ifndef CORELIB
struct r_lib_struct_t radare_plugin = {
.type = R_LIB_TYPE_ASM,
.data = &r_asm_plugin_padauk
};
#endif
Playing further with radare I noticed that there are mostly disassembler backends but only a few assemblers.
I kind of like the idea of having something table driven that allows for a certain symmetry of assembler and disassembler.
Also looks more like a datasheet. Again not complete but recognizes the instructions given, if you want more instructions its literally copy and paste from the website somewhere above.
Don't know if I'll be able to finish this soon though. Just to give an idea that this could be generalized I did the same for pic12 and at least it looks good
/* Padauk (dis)assembler plugin for radare 3.1 git - DocBen 2018
now looking more like a datasheet
*/
#include <r_asm.h>
#include <r_lib.h>
#include <string.h>
static struct {
char *op;
char *name;
ut8 args;
char *comment;
} ops[] = {
// memory mapped io i (6-bit)
// nth bit n (3-bit)
// memory address m (6 bit)
// memory address M (7 bit)
// immediate c (8 bit)
// address k (11 bit)
//"binary representation","mnemonic"(,"num parameters (could be inferred)"),"comment"
"0000.0000.0000.0000", "nop", 0, "does nothing (tm)",
"0000.0000.0110.0000", "addc a", 1, "A ← A + CF",
"0000.0000.11ii.iiii", "xor io,a", 2, "IO ← IO ^ A",
"0000.0001.10ii.iiii", "mov io,a", 2, "IO ← A",
"0000.0001.11ii.iiii", "mov a,io", 2, "A ← IO",
"0000.0010.cccc.cccc", "ret c", 1, "",
"0000.0011.1MMM.MMM0", "idxm M, a", 2, "[M] ← A (last bit of M set to 0, M must be word aligned, 2 cycles)",
"0000.0011.1MMM.MMM1", "idxm a, M", 2, "a ← [M] (last bit of M set to 1, M must be word aligned, 2 cycles)",
"0000.010n.nnii.iiii", "swapc io.n", 2, "",
"0000.0110.0MMM.MMMM", "comp a, M", 2, "",
"0010.000n.nnmm.mmmm", "t0sn m.n", 2, "",
"0011.0kkk.kkkk.kkkk", "goto k", 1, "goto address",
"0011.1kkk.kkkk.kkkk", "call k", 1, "call address",
"2222.2222.2222.2222", NULL, 0, "",
};
void bitstring(uint16_t val, char buffer[]) {
int size = 20;
buffer[--size] = 0;
while (size > 0) {
buffer[--size] = (val % 2 ? '1' : '0');
if ( size % 5 == 0 && size > 0) buffer[--size] = '.';
val = val >> 1;
}
}
int _PADAUKDisass (RAsm *a, RAsmOp *op, const ut8 *buf, int len) {
int i;
op->size = 2;
const char *buf_asm = "unknown";
char buf_bin[40];
bitstring(0x100 * buf[0] + buf[1], buf_bin);
for (i=0; ops[i].name != NULL ; i++) {
for (int j = 0; j < 20; j++) {
if (ops[i].op[j] != buf_bin[j]) {
if (ops[i].op[j] != '0' && ops[i].op[j] != '1') { // treat all letters as dont care
continue;
} else {
break;
}
}
if (j == 19) {
buf_asm = sdb_fmt ("%s = %s ; %s", buf_bin, ops[i].name, ops[i].comment);
r_strbuf_set (&op->buf_asm, buf_asm);
return op->size;
}
}
}
r_strbuf_set (&op->buf_asm, buf_asm);
return op->size;
}
int _PADAUKAss (RAsm *a, RAsmOp *op, const char *buf) {
int i;
op->size = 2;
ut16 opbuf = 0x4000;
const char *buf_hex = "unknown";
for (i = 0; ops[i].name != NULL; i++) {
if (!strncmp(ops[i].name, buf, strlen(ops[i].name))) {
//opbuf = ops[i].op;
buf_hex = sdb_fmt ("0x%.4X\n", opbuf);
break;
}
}
r_strbuf_set (&op->buf_hex, buf_hex);
return op->size;
}
RAsmPlugin r_asm_plugin_padauk = {
.name = "padauk2",
.arch = "padauk2",
.license = "LGPL3",
.bits = 16,
.desc = "Padauk (dis)assembler",
.disassemble = &_PADAUKDisass,
.assemble = &_PADAUKAss,
};
#ifndef CORELIB
struct r_lib_struct_t radare_plugin = {
.type = R_LIB_TYPE_ASM,
.data = &r_asm_plugin_padauk
};
#endif
PIC12
static struct {
char *op;
char *name;
ut8 args;
char *comment;
} ops[] = {
// direction d (1 bit)
// tri-state register t (2 bit)
// nth bit b (3-bit)
// register bank (3 bit)
// register f (5 bit)
// immediate c (8 bit)
// address k (8 bit)
// address K (9 bit)
//"binary representation","mnemonic"(,"num parameters"), "comment"
"0000.0000.0000.0000", "nop", 0, "No operation (MOVW 0,W)",
"0000.0000.0000.0010", "option", 0, "Copy W to OPTION register",
"0000.0000.0000.0011", "sleep", 0, "Go into standby mode",
"0000.0000.0000.0100", "clrwdt", 0, "Restart watchdog timer",
"0000.0000.0000.01tt", "tris t", 0, "Copy W to tri-state register (f = 1, 2 or 3)",
"0000.0000.0001.0BBB", "movlb k", 1, "Set bank select register to k",
"0000.0000.0001.1110", "return", 0, "Return from subroutine, W unmodified",
"0000.0000.0001.1111", "retfie", 0, "Return from interrupt; return & enable interrupts",
"0000.0000.001f.ffff", "movwf f", 1, "dest ← W",
"0000.0000.01df.ffff", "clr f,d", 2, "dest ← 0, usually written CLRW or CLRF f",
"0000.0000.10df.ffff", "subwf f,d", 2, "dest ← f−W (dest ← f+~W+1)",
"0000.0000.11df.ffff", "decf f,d", 2, "dest ← f−1",
"0000.0001.00df.ffff", "iorwf f,d", 2, "dest ← f | W, logical inclusive or",
"0000.0001.01df.ffff", "andwf f,d", 2, "dest ← f & W, logical and",
"0000.0001.10df.ffff", "xorwf f,d", 2, "dest ← f ^ W, logical exclusive or",
"0000.0001.11df.ffff", "addwf f,d", 2, "dest ← f+W",
"0000.0010.00df.ffff", "movwf f,d", 2, "dest ← f",
"0000.0010.01df.ffff", "comf f,d", 2, "dest ← ~f, bitwise complement",
"0000.0010.10df.ffff", "incf f,d", 2, "dest ← f+1",
"0000.0010.11df.ffff", "decfsz f,d", 2, "dest ← f−1, then skip if zero",
"0000.0011.00df.ffff", "rrf f,d", 2, "dest ← CARRY<<7 | f>>1, rotate right through carry",
"0000.0011.01df.ffff", "rlf f,d", 2, "dest ← F<<1 | CARRY, rotate left through carry",
"0000.0011.10df.ffff", "swapf f,d", 2, "dest ← f<<4 | f>>4, swap nibbles",
"0000.0011.11df.ffff", "incfsz f,d", 2, "dest ← f+1, then skip if zero",
"0000.0100.bbbf.ffff", "bcf f,b", 2, "Clear bit b of f",
"0000.0101.bbbf.ffff", "bsf f,b", 2, "Set bit b of f",
"0000.0110.bbbf.ffff", "btfsc f,b", 2, "Skip if bit b of f is clear",
"0000.0111.bbbf.ffff", "btfss f,b", 2, "Skip if bit b of f is set",
"0000.1000.cccc.cccc", "retlw c", 2, "Set W ← k, then return from subroutine",
"0000.1001.kkkk.kkkk", "call k", 2, "Call subroutine, 8-bit address k",
"0000.101K.KKKK.KKKK", "goto k", 2, "Jump to 9-bit address k",
"0000.1100.cccc.cccc", "movlw c", 2, "W ← c",
"0000.1101.cccc.cccc", "iorlw c", 2, "W ← c | W, bitwise logical or",
"0000.1110.cccc.cccc", "andlw c", 2, "W ← c & W, bitwise and",
"0000.1111.cccc.cccc", "xorlw c", 2, "W ← c ^ W, bitwise exclusive or",
"2222.2222.2222.2222", NULL, 0, "",
};
Nice job, would it be possible to make a github repo out of it?
Anyhow my idea for a "universal" instruction table was to have a list of objects like this, if this can somehow interest you:
opcode (16bit)
opcode_mask (16bit)
parameters (array)
[
{
type {IO/MEM/IMM/...}
length (in bits)
pos (bit n#)
}
]
There are not a lot of instructions that have more than one parameter encoded, but there are still some, so having an array of parameters descriptions covers everything. Let's call the object above "instr_t". You could write functions to create an instr_t from an assembly line, from an opcode or whatever. Also you could write functions to convert it to an opcode, to an assembly line or to emulate the thing (or maybe the emulator is just a big switch on the opcode and you do everything manually, idk). The point is, this should cover everything and be pretty flexible and somewhat clean.
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.
Also you may want the
make install rule to install the plugin under the user plugins directory (on my Linux system it's ~/.local/share/radare2/plugins) which is a bit less invasive
The architecture's engagingly minimal --- memory/memory architecture, one register! I actually have a self-hosting tiny compiler (<plug>
http://cowlark.com/cowgol </plug>) which would map very nicely onto this thing. Sadly, it doesn't have enough RAM to run the compiler on itself...
If anyone comes up with a simulator which will run PDK files, I'd love to know about it!
Uhm, I just noticed the PMS150/PMS150C opcodes are different from the PMS154C ones, while still being 14-bit wide. Seems like there are multiple encodings for the same instructions.
For instance:
mov A, 0xAA -> 0x17AA
mov <addr>, A -> 0x05C.. something like that
pcadd a -> 0x0017
wdreset -> 0x0030
But they're not totally different, most of the time it's just one bit off. Also the PDK fill pattern is 0x1FFF instead of 0x3FFF.
This may need some investigation.
edit: actually no value goes beyond 0x1FFF, I should have noticed that before. Seems like they shaved one bit off and the opcodes are 13-bit (?) if that makes any sense.
If anyone comes up with a simulator which will run PDK files, I'd love to know about it!
Maybe it could be added as a target for QEMU. It has already all kinds of infrastructure functions to implement a new target without too much effort, and it is very fast, because it translates the code to native assembler code on the fly, like a JIT compiler. And it has already functions for timers, interrupts etc.
If anyone comes up with a simulator which will run PDK files, I'd love to know about it!
I intend to get support for some Padauk devices into SDCC. For this, they'd need to be supported in the asxxxx fork that SDCC uses, the linker and the uCsim simulator. Support for the Single-Core devices will probably come first (as the lack of sp-relative addressing is a serious restriction for the multicore devices, requiring careful though on how to work around it).
Philipp
Uhm, I just noticed the PMS150/PMS150C opcodes are different from the PMS154C ones, while still being 14-bit wide. Seems like there are multiple encodings for the same instructions.
For instance:
mov A, 0xAA -> 0x17AA
mov <addr>, A -> 0x05C.. something like that
pcadd a -> 0x0017
wdreset -> 0x0030
But they're not totally different, most of the time it's just one bit off. Also the PDK fill pattern is 0x1FFF instead of 0x3FFF.
This may need some investigation.
edit: actually no value goes beyond 0x1FFF, I should have noticed that before. Seems like they shaved one bit off and the opcodes are 13-bit (?) if that makes any sense.
There are 3 different instruction sets¹ (each with some minor variation such as possible presence of mul). I assumed them to be 14, 15 and 16 bits. But from what you found it seems that they are 13, 14 and 16 bits. The 13-bit instruction set would be used on devices that have up to 1 kiloword of program memory, the 14-bit instruction set for up to 2 kilowords of program memory. The 16-bit instruction set would then be for larger and multicore devices.
Philipp
¹ According to an email by Padauk.
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.
Here's what I've figured out about the instruction set:
I've added some notes and questions at the end, if anyone can help clarify
these points, please let me know.
The PMS/C150 uses a 13 bit opcode:
00000 101ppppp MOV A,io-addr
00000 100ppppp MOV io-addr,A
10111 dddddddd MOV A,imm-data
00111 11aaaaaa MOV A,ram-addr
00101 11aaaaaa MOV ram-addr,A
00000 110aaaa1 LDT16 ram-addr-even
00000 110aaaa0 STT16 ram-addr-even
00000 111aaaa1 IDXM A,ram-addr-even
00000 111aaaa0 IDXM ram-addr-even,A
01001 11aaaaaa XCH ram-addr
00000 00110010 PUSHAF
00000 00110011 POPAF
10000 dddddddd ADD A,imm-data
00110 00aaaaaa ADD A,ram-addr
00100 00aaaaaa ADD ram-addr,a
00000 00010000 ADDC A
00110 10aaaaaa ADDC A,ram-addr
00100 10aaaaaa ADDC ram-addr,A
10001 dddddddd SUB A,imm-data
00110 01aaaaaa SUB A,ram-addr
00100 01aaaaaa SUB ram-addr,a
00000 00010001 SUBC A
00110 11aaaaaa SUBC A,ram-addr
00100 11aaaaaa SUBC ram-addr,A
01001 00aaaaaa INC ram-addr
01001 01aaaaaa DEC ram-addr
01001 10aaaaaa CLEAR ram-addr
00000 00011010 SR A
01010 10aaaaaa SR ram-addr
00000 00011100 SRC A
01011 00aaaaaa SRC ram-addr
00000 00011011 SL A
01010 11aaaaaa SL ram-addr
00000 00011101 SLC A
01011 01aaaaaa SLC ram-addr
00000 00011110 SWAP A
10100 dddddddd AND A,imm-data
00111 00aaaaaa AND A,ram-addr
00101 00aaaaaa AND ram-addr,A
10101 dddddddd OR A,imm-data
00111 01aaaaaa OR A,ram-addr
00101 01aaaaaa OR ram-addr,A
10110 dddddddd XOR A,imm-data
00111 10aaaaaa XOR A,ram-addr
00101 10aaaaaa XOR ram-addr,A
00000 011ppppp XOR io-addr,A
00000 00011000 NOT A
01010 00aaaaaa NOT ram-addr
00000 00011001 NEG A
01010 01aaaaaa NEG ram-addr
01110 bbbppppp SET0 io-addr.bit
01111 bbbppppp SET1 io-addr.bit
00011 bbb0aaaa SET0 ram-addr.bit
00011 bbb1aaaa SET1 ram-addr.bit
10010 dddddddd CEQSN A,imm-data
01011 10aaaaaa CEQSN A,ram-addr
01100 bbbppppp T0SN io-addr.bit
01101 bbbppppp T1SN io-addr.bit
00010 bbb0aaaa T0SN ram-addr.bit
00010 bbb1aaaa T1SN ram-addr.bit
00000 00010010 IZSN A
00000 00010011 DZSN A
01000 10aaaaaa IZSN ram-addr
01000 11aaaaaa DZSN ram-addr
110aa aaaaaaaa GOTO code-addr
111aa aaaaaaaa CALL code-addr
00001 dddddddd RET imm-data
00000 00111010 RET
00000 00111011 RETI
00000 00000000 NOP
00000 00010111 PCADD A
00000 00111000 ENGINT
00000 00111001 DISGINT
00000 00110110 STOPSYS
00000 00110111 STOPEXE
00000 00110101 RESET
00000 00110000 WDRESET
00000 00000000 TRAP // Assembler accepts, but same as NOP
General notes:
--------------
Full ram-addr range is 0x00-0x3B (60 bytes).
Instructions requiring ram-addr-even allow only 0x00-0x1E, lowest bit
is *NOT* encoded and is assumed to be zero.
Bit set/clear instructions on ram-addr allow only 0x00-0x0F.
Full io-addr range is 0x00-0x1F.
Instruction set notes:
----------------------
According to the data sheet, there are 4 flags: OV, Z, C, AC
Other than 'C' which can be accessed in the ADDC, SUBC, SRC and SLC
instructions, it is not clear what role the OV, Z and AC flags play.
There does not appear to be any instructions which access them.
The only conditionals are "compare ACC" and skip.
The only way I can see to access the other flags is via PUSHAF and then
reading them from memory (you would need to know exactly where SP points).
I'm wondering if this is "cut/paste" error and this processor does not
actually have OV, Z and AC (would make writing the simulator easier).
Perhaps someone with the actual part could write some test code using
arithmetic instructions and PUSHAF to test if these flags are actually
implemented on the PMS150?
The data sheet also says (page 18): (sic)
"The OTP program memory may contains the data, tables and interrupt entry."
I can see no capability in the instruction set to read data/tables from
program memory (other than immediate data).
Perhaps I have missed something?
The instruction set allows for a MAXIMUM of 64 bytes of RAM ... RAM
addresses are encoded in 6 bits. Yet, the indirect instruction IDXM uses
"pointer" values in memory which are two bytes in size. This makes no
sense to me as RAM is a very limited resource, and only one byte is needed
to address all of it. It also wastes instructions clearing the unused top
byte of the pointer - also a limited resource.
Perhaps "special" values in the upper byte allow indexed access to other
things, such as CODE memory or IO space ... but I can find no mention of
any such capability. Anyone know better?
PADAUK assembler notes:
-----------------------
HB@,LB@/HD@,LD@ can be used to access high/low byte of word data:
MOV A,hb@word_symbol
HA@,LA@ can be used to get high/low symbol address as immediate-data:
MOV A,ha@symbol
IO. can be used to specify an arbitrary IO address:
MOV A,io.2
I've not found a prefix for force a RAM access. The only way I've found
to access ROM is by explicitly declaring symbols with "word" and "byte"
directives. Anyone know better?
Regards,
Dave