Last visit was: Sat Sep 07, 2024 11:43 am
|
It is currently Sat Sep 07, 2024 11:43 am
|
PLASMA virtual machine on the OPC6
Author |
Message |
BigEd
Joined: Wed Jan 09, 2013 6:54 pm Posts: 1796
|
We don't have an overflow bit, so (I think) the unsigned case is easy: you do the subtraction and check the carry out. But the signed case is difficult, in effect you end up emulating a machine with an overflow bit. This is easy to get wrong in emulators, but surely it can be done correctly. This might help: Code: We can repeat the same table for subtraction. Note that subtracting a positive number is the same as adding a negative, so the conditions that trigger the overflow flag are:
SUBTRACTION SIGN BITS num1sign num2sign sumsign --------------------------- 0 0 0 0 0 1 0 1 0 *OVER* 0 1 1 (subtracting a negative is the same as adding a positive) *OVER* 1 0 0 (subtracting a positive is the same as adding a negative) 1 0 1 1 1 0 1 1 1 - from here. That said, Dave can probably help here more than I can.
|
Tue Aug 08, 2017 9:47 pm |
|
|
BigEd
Joined: Wed Jan 09, 2013 6:54 pm Posts: 1796
|
Thinking about this overnight, it's not necessary to emulate the overflow bit, but it is necessary to do a two-stage comparison. Just checking the case for ARM with gcc, we find this code sequence: Quote: cmp r2, r3 @ tmp136, tmp137 movge r3, #0 @, D.1752 movlt r3, #1 @, D.1752 Where the 'ge' predicate is "N=V" (Signed Greater or Equal) - this doesn't help us as we don't have a V. I'm going to have to rename your registers because r0 is special... SteveF wrote: ...how would the following C-ish snippets be translated into OPC6 assembler? Code: int r1, r2, r3 if (r2 < r3) { r1 = 1; } else { r1 = 0; }
Code: unsigned int r1, r2, r3 if (r2 < r3) { r1 = 1; } else { r1 = 0; }
Unsigned should be something like this: Code: mov r1, r0 cmp r2, r3 nc.inc r1, 1
Signed is a bit more difficult: we need (at least) one bit of sign extension. As the sign extension uses the sign bits, we can just check the sign bits first, and if they are the same, perform an unsigned comparison. (If they are different, we already have our answer.) This seems to work: Code: MACRO nop() z.mov r0,r0 ENDMACRO
MACRO cmpunsigned() mov r1, r0 cmp r2, r3 nc.inc r1, 1 ENDMACRO
MACRO cmpsigned(__signsdiffer, __done) mov r1, r2 xor r1, r3 mi.inc pc, __signsdiffer-PC
cmpunsigned() inc pc, __done-PC
__signsdiffer: mov r1, r0 mov r0, r2 mi.inc r1, 1
__done:
ENDMACRO
mov r3, r0, 3 mov r2, r0, 2 cmpunsigned() nop()
mov r3, r0, 4 mov r2, r0, 4 cmpunsigned() nop()
mov r3, r0, 6 mov r2, r0, 7 cmpunsigned() nop()
mov r3, r0, 0x33 mov r2, r0, 0x22 cmpsigned(L001, L001a) nop()
mov r3, r0, 0x44 mov r2, r0, 0x44 cmpsigned(L002, L002a) nop()
mov r3, r0, 0x66 mov r2, r0, 0x77 cmpsigned(L003, L003a) nop()
mov r3, r0, 3 mov r2, r0, -2 cmpsigned(L004, L004a) nop()
mov r3, r0, -2 mov r2, r0, 3 cmpsigned(L005, L005a) nop()
mov r3, r0, -1 mov r2, r0, -255 cmpsigned(L006, L006a) nop()
halt r0,r0,0x999 I made these notes while thinking about this: A signed byte would be [-128..127] We can compare two bytes [-128..127] <? [-128..127] presumably by subtraction [-128..127] - [-128..127] = [-255..255] which fits in 9 bits as a signed value - but not in 8. Sign-extending into two top bytes and then doing a two-byte subtraction would be overkill. However, we don't need a two-byte subtraction: for comparison, we can first compare the top bytes and only if they match do we compare the bottom bytes - the original ones - as unsigned. As the top byte is just a sign extension of the sign bits, we just can compare the signs first, and then compare the low bytes (original bytes) as unsigned if the signs match. is -128 < -1? Yes, because 128 < 255 is 128 < 1? No, because 128 !< 1
|
Wed Aug 09, 2017 6:44 am |
|
|
SteveF
Joined: Sat Aug 05, 2017 6:57 pm Posts: 26
|
Thanks Ed, looks good - I'll give that a try! I've heavily edited the following so apologises if anyone has read it beforehand: I completely messed up the commands to assemble and then emulate and went down a blind alley as a result, but I still find the below snippet odd. It doesn't directly matter because I am just executing some horrible mangled up verison of my code, not what I thought, but I would expect this emulation trace to make sense anyway and it doesn't, so maybe I can learn something by asking this anyway. Code: $ python opc6emu.py plasma.o mem|grep -C10 '^0000' [...] 0218 : 0daa : lsr r10,r10 : 0 0 0 0 1 : 0000 effe 03aa f000 f000 11b5 0000 0000 0000 0100 0000 000a 2000 effe 0262 0219 0219 : 17aa 021e : ld r10,r10,0x021e : 0 0 0 0 1 : 0000 effe 03aa f000 f000 11b5 0000 0000 0000 0100 0000 000a 2000 effe 0262 021b 021b : 09ae : jsr r14,r10 : 0 0 0 0 0 : 0000 effe 03aa f000 f000 11b5 0000 0000 0000 0100 025e 000a 2000 effe 0262 021c 025e : 28de : push r14,r13 : 0 0 0 0 0 : 0000 effe 03aa f000 f000 11b5 0000 0000 0000 0100 025e 000a 2000 effe 021c 025f 025f : 2810 : push r0,r1 : 0 0 0 0 0 : 0000 effe 03aa f000 f000 11b5 0000 0000 0000 0100 025e 000a 2000 effd 021c 0260 0260 : 190e 030a : jsr r14,r0,0x030a : 0 0 0 0 0 : 0000 effd 03aa f000 f000 11b5 0000 0000 0000 0100 025e 000a 2000 effd 021c 0262 030a : 1406 0001 : add r6,r0,0x0001 : 0 0 0 0 0 : 0000 effd 03aa f000 f000 11b5 0000 0000 0000 0100 025e 000a 2000 effd 0262 030c 030c : 3a06 0002 : cmp r6,r0,0x0002 : 0 0 0 0 0 : 0000 effd 03aa f000 f000 11b5 0001 0000 0000 0100 025e 000a 2000 effd 0262 030e 030e : 60ef : nz.mov r15,r14 : 0 0 1 0 0 : 0000 effd 03aa f000 f000 11b5 0001 0000 0000 0100 025e 000a 2000 effd 0262 030f 0262 : 29df : pop r15,r13 : 0 0 1 0 0 : 0000 effd 03aa f000 f000 11b5 0001 0000 0000 0100 025e 000a 2000 effd 0262 0263 0000 : 0000 : mov r0,r0 : 0 0 1 0 0 : 0000 effd 03aa f000 f000 11b5 0001 0000 0000 0100 025e 000a 2000 effe 0262 0001 0001 : 0000 : mov r0,r0 : 0 0 0 0 1 : 0000 effd 03aa f000 f000 11b5 0001 0000 0000 0100 025e 000a 2000 effe 0262 0002 0002 : 0000 : mov r0,r0 : 0 0 0 0 1 : 0000 effd 03aa f000 f000 11b5 0001 0000 0000 0100 025e 000a 2000 effe 0262 0003
I don't understand why the pop at instruction 0262 transfers control to address 0. I pushed 021c at instruction 025e and I don't see how any of the intervening instructions can interfere with popping this value. I'm sure it's something really obvious in hindsight but I just can't see it... Cheers. Steve
|
Wed Aug 09, 2017 8:03 pm |
|
|
BigEd
Joined: Wed Jan 09, 2013 6:54 pm Posts: 1796
|
Ah - looks like your r1 stack and your r13 stack are both pointing to the same place - that can't be right.
|
Wed Aug 09, 2017 9:01 pm |
|
|
SteveF
Joined: Sat Aug 05, 2017 6:57 pm Posts: 26
|
Thank you, that makes sense!
My code actually had a bug earlier and the machine was in a funny state by this point anyway, but I wanted to understand what was going on in that trace. Cheers!
|
Wed Aug 09, 2017 9:03 pm |
|
|
Revaldinho
Joined: Tue Apr 25, 2017 7:33 pm Posts: 32
|
Steve, Is it this bit Code: 025e : 28de : push r14,r13 : 0 0 0 0 0 : 0000 effe 03aa f000 f000 11b5 0000 0000 0000 0100 025e 000a 2000 effe 021c 025f 025f : 2810 : push r0,r1 : 0 0 0 0 0 : 0000 effe 03aa f000 f000 11b5 0000 0000 0000 0100 025e 000a 2000 effd 021c 0260 0260 : 190e 030a : jsr r14,r0,0x030a : 0 0 0 0 0 : 0000 effd 03aa f000 f000 11b5 0000 0000 0000 0100 025e 000a 2000 effd 021c 0262
You push r14 onto the stack pointed at by r13, but then immediately push 0 onto the same location - r13 and r1 both have the same address at the start of each push instruction. Later you expect to pop off the value you pushed onto r13, but actually get back the value from the second push. EDIT - oops. Just seen Ed's post while I was typing that:) R
|
Wed Aug 09, 2017 9:10 pm |
|
|
SteveF
Joined: Sat Aug 05, 2017 6:57 pm Posts: 26
|
Thanks anyway, I appreciate the help! And now I feel twice as stupid.
|
Wed Aug 09, 2017 9:21 pm |
|
|
BigEd
Joined: Wed Jan 09, 2013 6:54 pm Posts: 1796
|
Don't worry - it took me ages to see it! And this machine is just big enough to confuse, sometimes. (If we didn't have the self-imposed limit of one page on the emulator, it would be nice to log the data reads and writes.)
|
Wed Aug 09, 2017 9:25 pm |
|
|
Revaldinho
Joined: Tue Apr 25, 2017 7:33 pm Posts: 32
|
Well, since I'm here, I'll let you know that I've checked in a bit of a work in progress which is opc6byteasm.py, specifically for the PLASMA VM work. The new assembler builds up the data a byte at a time rather than a word at a time. All the original syntax (including the BYTE additions) works as described previously, generating word aligned data and padding where necessary. So, although the listing output is different from the original, the final hex files produced are exactly the same when run on the existing benchmarks. All of those assemble and run fine on the emulator. For the VM work then there is some new syntax. Code: UBYTE expr, expr, expr... UWORD expr, expr, expr... UBSTRING "string", "string",...
All have the same syntax as the BYTE, WORD and BSTRING but this time do not create padded data, and do not need to be on word aligned boundaries. e.g. Code: UBYTE 0x01 UWORD 0x02,0x03 UBYTE 0x04 UBYTE 0x05 ...will generate a run of bytes 0x01, 0x02, 0x03, 0x04, 0x05 Of course this means that you can now generate sequences that will cause the next OPC instruction to be on an unaligned byte. That has to be illegal and will be reported as an error. To fix that you need to use the new directive. And then there's the thorny issue of labels. Really there are two separate spaces here: byte space for the VM and word space for the OPC code. I think you said that they don't need to mix. Now, I may not have the right solution here, but what I've done is make two types of label so deciding which space you're in is up to the user. The :B form will generate a label pointing at a byte address. The :W form (you can omit the W because that's the original label syntax) will generate a label pointing at a word address. Again you can't put a word label on an unaligned word, so you will need to use that ALIGN directive if that might happen. If you want to label an aligned location with both a byte and word label for some reason, then you must make sure that the labels have different names to avoid a clash - i.e. the :B, :W suffix are not part of the label name. So, there it is. It's definitely not finished but it does assemble all the existing word-oriented test cases, so please give it a try. Hopefully this is more along the lines of what you need for the VM, but let me know (maybe PM me) if it needs more changes - the labels perhaps - or other additions. I think you might need some kind of 'current-PC' for example as the OPC has in the word-space. R
|
Wed Aug 09, 2017 9:28 pm |
|
|
SteveF
Joined: Sat Aug 05, 2017 6:57 pm Posts: 26
|
Thanks, that looks really good - but I think you're right, I do need some kind of 'current byte space address' in order to be able to calculate relative distances (the '*' in the code snippets I posted the other day. If you can add that I think this will be exactly what I want and I can give it a try. Edited to add: That would certainly be nice, but I could always add dummy extra labels where I want to use this hypothetical 'current byte space address', i.e. write: Code: UBYTE 0x05 branch_target:B UBYTE 0x10, 0x20 fake_star:B UWORD branch_target-fake_star
So it's not essential if this is difficult to add. (Perfect timing too as I've been using a couple of branch instructions in my latest VM tests tonight and calculating the byte displacement by hand is subject to hand-waving. "It doesn't work, oh, but wait, I put displacement 17 and it should be 18, yeah, I counted wrong, I'm not just fiddling it because it might start working then, honest". )
Last edited by SteveF on Wed Aug 09, 2017 9:45 pm, edited 1 time in total.
|
Wed Aug 09, 2017 9:32 pm |
|
|
SteveF
Joined: Sat Aug 05, 2017 6:57 pm Posts: 26
|
I've just updated the plasma.s in my repo to use the new UBYTE/UWORD directives and they're working a treat! Thanks very much - as I say, the 'current byte offset' aka '*' would be helpful, but the workaround of adding extra labels works fine and it's still a huge improvement on doing it manually via the BYTE directive.
|
Wed Aug 09, 2017 9:43 pm |
|
|
Revaldinho
Joined: Tue Apr 25, 2017 7:33 pm Posts: 32
|
Steve, Just pushed another update with the 'current byte location' addition: _BPC_ Here's an assembled fragment using this new token: Code: 2479 L11:B 2479 11 22 UBYTE 0x11, 0x22 247b L12:B 247b 33 44 55 UBYTE 0x33, 0x44, 0x55 247e L13:B 247e 66 77 88 99 UBYTE 0x66, 0x77, 0x88, 0x99 2482 82 24 UWORD _BPC_ 2484 84 24 UWORD _BPC_ 2486 0b UBYTE _BPC_ - L12
In the two UWORD statements you can see that _BPC_ is correctly updated to point at the location of each statement. The expression in the final UBYTE evaluates the difference between current location and label L12, and counting backwards that looks right to me (i.e. 11) . _BPC_ can be used like any other symbol with any expression that would be valid in Python. R
|
Thu Aug 10, 2017 7:22 pm |
|
|
Revaldinho
Joined: Tue Apr 25, 2017 7:33 pm Posts: 32
|
and by the way, Ed wrote Quote: (If we didn't have the self-imposed limit of one page on the emulator, it would be nice to log the data reads and writes.)
so I added the logging of all memory and IO activity to the emulator trace in the same release. And why not, as the late Barry Norman might have said. R
|
Thu Aug 10, 2017 7:52 pm |
|
|
SteveF
Joined: Sat Aug 05, 2017 6:57 pm Posts: 26
|
Thanks Revaldinho, that's great stuff. Behold... Code: steven@riemann:~/src/PLASMA-opc6$ ./doit.sh test5.pla [...] steven@riemann:~/src/PLASMA-opc6$ python annotate.py mem | grep a000: a000: 0100 0302 0504 0706 0908 000a 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
I'm sure it will break on compiled code I haven't tested, but it does seem to compile and run test[1-5].pla OK. (I deliberately haven't done hackery to add :B suffixes to all the label types I suspect will need it, to force me to think about them in context.) This should make it a lot more fun/quick to work through test code to implement more VM opcodes. I do have a couple of requests/bits of feedback though: - EQU gives an error if the current position isn't word-aligned - is this necessary? Isn't EQU just a way of setting a label to a constant value? This isn't causing me any big problems, but I thought I'd mention it.
- Would it be possible to have the assembler return with a non-0 exit code if assembly fails?
Thanks again! Steve
|
Thu Aug 10, 2017 9:21 pm |
|
|
Revaldinho
Joined: Tue Apr 25, 2017 7:33 pm Posts: 32
|
Steve
EQU is a bit limited. In fact I'm not sure it works at all with labels, or at least not labels which are as yet undefined. Have you got some example use of it which I can see ?
Exiting with non-zero status on errors ? Yes, I'll add that.
R
|
Thu Aug 10, 2017 9:25 pm |
|
Who is online |
Users browsing this forum: CCBot, SemrushBot and 2 guests |
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot post attachments in this forum
|
|