Last visit was: Wed Oct 09, 2024 8:22 pm
It is currently Wed Oct 09, 2024 8:22 pm



 [ 11 posts ] 
 16-bit stack CPU design 
Author Message

Joined: Wed Oct 23, 2019 1:52 am
Posts: 9
Location: Sacramento, CA
I've had a vague interest in stack-oriented CPU designs for a while, but it took me a while to settle in on an architecture that seemed flexible-yet-achievable enough for a potential hardware project down the road; after a while of fiddling around with different approaches, I ended up with something inspired primarily by the DG Nova architecture (and the PDP-8's OPR instructions,) in the sense of trying to pack a large number of functions into the primary opcode formats and choosing sub-operations that make it possible to synthesize additional operations out of simpler ones. I'd like to do a hardware implementation at some point, but for the moment I'm using it as a VM architecture for a little Parallax Propeller project I'm working on; after much tinkering, I've gotten the primary instruction groups down to 2.8 and 3.6 µs. Anyway, on to the description:

This is a 16-bit, byte-addressable CPU using two stacks in main memory (hardware implementation would likely include some kind of caching.) There are four CPU registers: the Instruction Pointer (IP,) the Interrupt Mask/Carry (MSK,) and the Data and Call Stack Pointers (DSP and CSP.) The top few stack items are referred to as pseudo-registers TOS (top-of-stack, item #0) NOS (next-on-stack, item #1) NNS (next-next-on-stack, #2,) and TCS (top-of-call-stack.) Instructions and stack items are a full 16-bit word; the address registers (IP, DSP, and CSP) implement only the 15 MSBs and are therefore always fixed to a word address. The instruction groups are as follows:

Code:
FEDCBA9876543210
----------------
0NNNNNNNNNNNNNNN  Push constant:
 N                  Number (15-bit unsigned integer)
1000JRFFS<>NCCI!  Arithmetic operations:
    J               Jump (IP <- TOS)
     R              Prepare for Return (TCS <- IP)
      FF            ALU Function (0: Add w./Carry, 1: AND, 2: OR, 3: XOR)
        S           B-operand Source (0: Consume NOS, 1: Constant 0xFFFF)
         <>         Rotate TOS (0: No, 1: Right, 2: Left, 3: Swap bytes)
           N        Alter Carry if Negative (0: Always alter, 1: Only if TOS < 0)
            CC      Alter Carry (0: No, 1: Complement, 2: Clear, 3: Set)
              I     Increment TOS (0: No, 1: Increment TOS)
               !    Complement TOS (0: No, 1: Ones-complement TOS)
1001B+-<>sIDMCcb  Stack/control operations:
    B               Break to software interrupt handler
     +              Duplicate TOS
      -             Drop TOS
       <>           Transfer TOS/TCS (0: None, 1: TOS -> TCS, 2: TOS <- TCS, 3: TOS <-> TCS)
         s          Save/Load Registers on call stack (0: Load, 1: Save)
          I         Save/Load IP (0: No, 1: Yes)
           D        Save/Load DSP
            M       Save/Load MSK
             C      Swap TOS w./CSP
              c     Swap TOS w./NNS
               b    Swap TOS w./NOS
1010BXIOOOOOOOOO  Load Byte/Word
    B               Byte (0: Word, 1: Byte)
     X              Extend (0: No, 1: Yes)
      I             Index source (0: Consume TOS, 1: IP)
       O            Offset (9-bit byte offset, sign-extended)
1011BXIOOOOOOOOO  Store Byte/Word
1100OOOOOOOOOOOO  Branch if Zero (Consume TOS and branch if zero)
    O               Offset (12-bit word offset, sign-extended)
1101OOOOOOOOOOOO  Branch if Positive
1110OOOOOOOOOOOO  Branch if Negative
1111OOOOOOOOOOOO  Branch Always

As you can see, the two primary instruction groups (besides the constant-push operation) are composed of multiple fields/flags for different sub-operations; these are (mostly) executed in-order, LSB-first. (Exceptions are the modifier flags for other fields - the Alter Carry if Negative flag and the load/save switch for register saving and retrieval. The load/save operations also operate in reverse order when saving, so that grouped save and restore operations record and restore the same operating context.) Any non-negative integer is immediately pushed to the stack as-is; values from 0x8000 - 0xFFFF can be constructed by pushing the logical or additive inverse and then ones- or twos-complementing it using the first two sub-operations in the arithmetic group.

This isn't set in stone yet - I've yet to do any serious coding tests, and there's a few things I'm not entirely sure of (the order of operations between carry-manipulation, rotate-with-carry, and ALU operation, particularly.) Particular details are also implementation-dependent at the moment (the Propeller implementation keeps TOS entirely in register and DSP always points at NOS, which I don't care for, but which resulted in a significant speed-up,) and there's no specified I/O model (the Propeller implementation, again, uses memory-mapped I/O for convenience's sake, but if I ever do a hardware implementation I'd probably try to sneak port-mapped I/O in there by cannibalizing the mildly nonsensical write-byte-sign-extended and read-word-sign-extended instruction combinations.)

I do have the Propeller implementation pretty much completed, but I haven't tested it yet...

_________________
"'Legacy code' often differs from its suggested alternative by actually working and scaling." - Bjarne Stroustrup


Wed Oct 23, 2019 5:36 am WWW
User avatar

Joined: Fri Oct 20, 2017 7:54 pm
Posts: 8
Quote:
I do have the Propeller implementation pretty much completed, but I haven't tested it yet...

I'm all ears! :mrgreen:

_________________
"Stay OmmmMMMmmmPtimistic!" — yeti
"Logic, my dear Zoe, merely enables one to be wrong with authority." — The 2nd Doctor
"Don't we all wait for SOMETHING-ELSE-1.0?" — yeti


Wed Oct 23, 2019 7:27 am

Joined: Wed Jan 09, 2013 6:54 pm
Posts: 1799
yes, sounds great!


Wed Oct 23, 2019 9:28 am

Joined: Wed Oct 23, 2019 1:52 am
Posts: 9
Location: Sacramento, CA
As mentioned, this has been in no way tested yet (really need to get set up with a Propeller toolchain and simulator...) But here it is anyway:

Code:
   ; Stack-CPU instruction set interpreter for Parallax Propeller
   ; Alternate implementation - no NOS caching, TOS is register-only, and DSP
   ; points to NOS; want to try and cut down the amount of juggling needed.
   
   org 0
   
doALU:                     ; Handle group 0x8 (ALU) opcodes
   ; Initial branch-off to init code - replaced with the first instruction of
   ; the ALU group handler routine during init time.
   jmp #doInit
   ; Above to be replaced with:
   ; test opcode, #1 wz      ; is the ! bit set?
   ; Here follows the rest of the group 0x8 code.
   if_nz xor tos, const_ffff   ; if yes, ones-complement TOS
   test opcode, #2 wz         ; is the I bit set?
   if_nz add tos, #1         ; is yes, increment TOS
   ; before checking the carry bits, check the only-if-negative flag
   test opcode, #16 wz         ; is the if-negative flag set?
   if_nz test tos, const_8000 wc   ; if yes, is TOS negative?
   if_nz_and_nc andn opcode, #12   ; if not, clear the carry bits
   test opcode, const_0400 wc   ; are we saving IP to TCS?
   if_c sub csp, #2         ; if yes, decrement CSP
   if_c wrword ip, csp         ; and save it to the stack
   test opcode, #8 wz         ; check the carry bits
   if_nz mov carry, #0         ; CC %1x: clear carry
   test opcode, #4 wz
   if_nz xor carry, #1         ; CC %x1: complement carry
   ; new approach: ALU function happens before the shift/rotate, Nova-style
   test opcode, #128 wz      ; is the ALU source 0xFFFF?
   if_nz mov tmp, const_ffff   ; if yes, use 0xFFFF
   if_z rdword tmp, dsp      ; if no, get the current NOS
   if_z add dsp, #2         ; if we're using NOS, increment DSP
   test opcode, const_0600 wc   ; check the ALU operation bits
   test opcode, const_0400 wz   ; Z clear if %10 or %11, C set if %01 or %10
   if_z_and_nc add tos, tmp   ; FF %00: add with carry
   if_nz_or_c shl carry, #16   ; if no add, carry-in goes to carry-out
   if_z_and_c and tos, tmp      ; FF %01: AND
   if_nz_and_c or tos, tmp      ; FF %10: OR
   if_nz_and_nc xor tos, tmp   ; FF %11: XOR
   add tos, carry
   test opcode, #64 wc         ; check the rotate bits
   test opcode, #32 wz
   if_nc_and_z jmp #doRotate
   ; <> % 11: swap bytes
   test tos, const_10000 wc   ; copy carry-out to Propeller carry
   if_nc_and_nz mov tmp, tos   ; make a copy
   if_nc_and_nz shl tmp, #16   ; shift it left
   if_nc_and_nz or tos, tmp   ; and combine them
   if_nc_and_nz rol tos, #8   ; and rotate left
rotDone:
   and tos, const_ffff         ; clear any extraneous MSBs
   if_c mov carry, #1         ; copy carry-out to carry flag
   if_nc mov carry, #1
   test opcode, const_0800 wz   ; are we jumping?
   if_nz mov ip, tos         ; if yes, copy TOS to IP
   if_nz rdword tos, dsp      ; and bump NOS to TOS
   add dsp, #2               ; and point DSP at new NOS
   jmp #doNext
   
doRotate:
   if_z test tos, const_10000 wc   ; <> %10: rotate left
   if_z rcl tos, #1         ; shift it left
   if_z test tos, const_10000 wc   ; copy carry-out to Propeller carry
   if_nz shr tos, #1 wc      ; <> %01: rotate right
   jmp #rotDone
   
const_10000   long $10000
const_ffff   long $ffff
const_ff00   long $ff00
const_8000   long $8000
const_7fe0   long $7fe0
const_0800   long $800
const_0600   long $600
const_0400   long $400
const_0200   long $200
const_rdbyte   rdbyte tos, tmp
const_rdword   rdword tos, tmp
const_wrbyte   wrbyte tos, tmp
const_wrword   wrword tos, tmp
const_doALU   test opcode, #1 wz
   
tos   long 0               ; Top-of-stack pseudo-register
carry   long 0            ; Carry flag
mask   long 254         ; Interrupt-enable mask
   
   res ($40 - $)         ; pad to 0x040 (already filled up!)
doControl:               ; Handle group 0x9 (control/stack) opcodes
   mov tmp, tos         ; save TOS just in case
   mov tmp2, dsp         ; prepare a pointer to NNS
   add tmp2, #2
   test opcode, #1 wz      ; is the swap-B bit set?
   test opcode, #2 wc      ; is the swap-C bit set?
   if_nz rdword tos, dsp   ; if swap-B is set, bump NOS to TOS
   or carry, mask         ; prep mask/carry register just in case
   mov tmp3, tos         ; save the current TOS
   if_nz wrword tmp, dsp   ; if swap-B is set, push TOS to NOS
   test opcode, #64 wz      ; is the save bit set?
   if_nz jmp #saveRegs      ; if yes, save registers to the call stack
   rdword tos, tmp2      ; bump NNS to TOS
   test opcode, #32 wz      ; are we loading IP?
   if_nc mov tos, tmp3      ; restore TOS if we did that wrong above
   if_c wrword tmp3, tmp2   ; if swap-C is set, push TOS to NNS
   test opcode, #4 wc      ; is the swap-CSP bit set?
   if_c xor tos, csp      ; if yes, swap TOS & CSP
   if_c xor csp, tos
   if_c xor tos, csp
   test opcode, #16 wc      ; are we loading DSP?
   mov tmp, dsp         ; save DSP in case we need to restore it later
   if_nz rdword ip, csp   ; if we're loading IP, pop it from the call stack
   if_nz add csp, #2      ; and increment CSP
   test opcode, #8 wz      ; are we loading M?
   rdword dsp, csp         ; if we're loading DSP, pop it from the call stack
   if_c add csp, #2      ; and increment CSP
   if_nc mov dsp, tmp      ; restore DSP if we did that wrong
   if_nz rdword carry, csp   ; if we're loading M, pop it from the call stack
   if_nz add csp, #2      ; and increment CSP
   if_nz mov mask, carry   ; and separate the mask and carry fields
saveRegsDone:
   test opcode, #384 wc   ; check the stack-transfer bits
   test opcode, #256 wz   ; %00: none, %01: TOS-TCS, %10: TCS-TOS, %11: swap
   if_c_and_nz jmp #fromTCS
   if_nc_and_nz jmp #swapStacks
   ; <> %01: push TOS to TCS
   rdword tmp, dsp         ; get NOS in case we need it
   if_c_and_z add dsp, #2   ; increment DSP
   if_c_and_z sub csp, #2   ; decrement CSP
   if_c_and_z wrword tos, csp   ; push TOS to TCS
   if_c_and_z mov tos, tmp   ; bump NOS to TOS
   andn mask, #1         ; fix mask in case it was loaded
transferDone:
   and carry, #1         ; fix carry in case it was loaded
   nop                  ; was a pointless instruction
   test opcode, const_0600 wc   ; check the drop/dup bits
   test opcode, const_0400 wz   ; Z clear if %01 or %11, C set if %01 or %10
   if_nz rdword tos, dsp      ; if dup or drop/dup, set TOS to NOS
   if_c_and_z sub dsp, #2      ; if dup, decrement DSP
   if_c_and_nz add dsp, #2      ; if drop, increment DSP
   if_c_and_z wrword tos, dsp   ; if dup, set NOS to TOS
   test opcode, const_0800 wz   ; is the break bit set?
   if_nz mov ip, breaker   ; if yes, point IP to SWI service address
   ; Control group falls through to:
doNext:
   rdword opcode, ip      ; get next instruction
   test mask, ina wz      ; check enabled interrupt sources
   if_nz jmp #doInterrupt   ; if anything's firing, address it
   add ip, #2            ; increment IP
   shr opcode, #15 wz,nr   ; is the MSB clear?
   if_z jmp #doConstant   ; if yes, it's a 15-bit constant
   mov tmp, opcode         ; if no, turn the opcode into a dispatch address
   shr tmp, #12         ; move the group-select bits all the way right
   shl tmp, #6            ; move them back into position (clear the rest)
   jmp tmp               ; jump to the indicated address
   
ip   long 0               ; Instruction Pointer register
dsp   long 0               ; Data Stack Pointer register
csp   long 0               ; Call Stack Pointer register
   
   ; 1 free word
   
   res ($80 - $)         ; pad to 0x80
doLoad:                  ; Handle group 0xA (load) opcodes
   mov tmp, opcode         ; copy the opcode to turn it into an offset
   shl tmp, #23         ; sign-extend the offset
   asr tmp, #23
   test opcode, const_0800 wc   ; is the byte bit set?
   if_nc movi :read, const_rdbyte   ; if yes update the instruction
   if_c movi :read, const_rdword   ; if no, update it the other way
   test opcode, const_0200 wz   ; is the index-from-TOS bit set?
   if_z sub dsp, #2      ; if no, decrement DSP
   if_nz add tmp, tos      ; if yes, add TOS to offset
   if_z wrword tos, dsp   ; if no, push TOS down to NOS
   if_z add tmp, ip      ; and add IP to offset
   test opcode, const_0400 wz   ; is the sign-extend bit set?
:read
   rdword tos, tmp         ; fetch the value
   if_c and tos, #255      ; if the byte bit is set, clear the MSBs
   if_nz test tos, #128 wc   ; if we're sign-extending, test the byte MSB
   if_nz muxc tos, const_ff00   ; and copy it to the MSBs
   jmp #doNext
   
swapStacks:               ; <> %11: swap TOS and TCS
   rdword tmp, csp         ; get TCS
   andn mask, #1         ; fix mask in case it was loaded
   wrword tos, csp         ; push TOS to TCS
   mov tos, tmp         ; push TCS to TOS
   jmp #transferDone
   
fromTCS:               ; <> %10: push TCS to TOS
   andn mask, #1         ; fix mask in case it was loaded
   rdword tmp, csp         ; read TCS
   add csp, #2            ; increment CSP
   sub dsp, #2            ; decrement DSP
   wrword tos, dsp         ; push TOS to NOS
   mov tos, tmp         ; pop TCS to TOS
   jmp #transferDone
   
saveRegs:               ; load registers from the call stack
   rdword tmp, tmp2      ; get NNS
   if_nc mov tmp3, tmp      ; if swap-C is clear, push NNS back to NNS
   if_c mov tos, tmp      ; if swap-C is set, bump NNS to TOS
   wrword tmp3, tmp2      ; store the appropriate value of NNS back
   test opcode, #4 wc      ; is the swap-CSP bit set?
   if_c xor tos, csp      ; if yes, swap TOS & CSP
   if_c xor csp, tos
   if_c xor tos, csp
   test opcode, #8 wz      ; are we saving M?
   if_nz sub csp, #2      ; if yes, decrement CSP
   if_nz wrword carry, csp   ; if we're saving M, put it on the call stack
   test opcode, #16 wc      ; are we saving DSP?
   if_c sub csp, #2      ; if yes, decrement CSP
   if_c wrword dsp, csp   ; if we're saving DSP, put it on the call stack
   test opcode, #32 wz      ; are we saving IP?
   if_nz sub csp, #2      ; if yes, decrement CSP
   if_nz wrword ip, csp   ; and put it on the call stack
   and carry, #1         ; restore carry to normalcy
   jmp #saveRegsDone
   
doConstant:               ; push 15-bit constant to TOS
   sub dsp, #2            ; decrement DSP
   wrword tos, dsp         ; push TOS to NOS
   mov tos, opcode         ; place the constant in TOS
   rdword tmp, ip         ; delay to align with 0.4 us "cycle time"
   jmp #doNext
   
   ; 11 free words
   
   res ($c0 - $)         ; pad to 0xC0
doStore:               ; Handle group 0xB (store) opcodes
   mov tmp, opcode         ; turn the opcode into an offset
   shl tmp, #23         ; sign-extend the offset
   asr tmp, #23
   test opcode, const_0200 wc   ; is the index-from-TOS bit set?
   if_c add tmp, tos      ; if yes, add TOS to the offset
   if_c rdword tos, dsp   ; and bump NOS to TOS
   if_nc add tmp, ip      ; if no, add IP to the offset
   test opcode, const_0800 wz   ; is the byte-write bit set?
   if_nz movi :write, const_wrbyte   ; if yes, update the instruction
   test opcode, const_0400 wz   ; is the sign-extend bit set?
   if_nz test tos, #128 wc   ; if yes, sign-extend the LSB
   if_nz muxc tos, const_ff00
:write
   wrword tos, tmp         ; store the item to memory at the specified address
   movi :write, const_wrword   ; reset the instruction to default
   if_c add dsp, #2      ; if index-from-TOS is set, increment DSP
   rdword tos, dsp         ; bump NOS or NNS to TOS
   add dsp, #2            ; and increment DSP
   jmp #doNext
   
doInit:                  ; do initial setup for this CPU core
   mov doALU, const_doALU   ; overwrite the initial jump instruction
   mov mask, #0         ; disable all maskable interrupts
   mov ip, #32            ; initialize IP to the shared reset location
   cogid breaker         ; get the ID of this cog
   mov tos, breaker      ; initialize TOS to the cog ID
   shl breaker, #2         ; turn it into a quad-word index
   ; set up the stacks at the top of Propeller RAM
   mov dsp, const_7fe0      ; initialize DSP to 0x7FE0 - (ID * 4 words)
   sub dsp, breaker
   mov csp, const_8000      ; initialize CSP to 0x8000 - (ID * 4 words)
   sub csp, breaker
   rdword tmp, ip         ; delay to align with 0.4 us "cycle time"
   jmp #doNext
   
   ; 34 free words
   
   res ($100 - $)         ; pad to 0x100
doBranchIfZero:            ; handle group 0xC (branch-if-zero) opcodes
   test tos, const_ffff wz   ; is TOS zero?
   rdword tos, dsp         ; bump NOS to TOS
   add dsp, #2            ; increment DSP
   shl opcode, #20         ; sign-extend the offset
   asr opcode, #19         ; shift it down to a word offset
   if_z add ip, opcode      ; if TOS was zero, branch
   rdword tmp, ip         ; delay to align with 0.4 us "cycle time"
   jmp #doNext
   
doInterrupt:            ; identify and dispatch interrupt handler
   mov tmp, mask         ; get a copy of the interrupt mask
   or mask, carry         ; combine mask/carry for saving
   and tmp, ina         ; mask out any interrupts we're ignoring
   sub csp, #2            ; decrement CSP
   wrword mask, csp      ; save M to the stack
   sub csp, #2            ; decrement CSP
   mov tmp2, #254         ; prepare to mask out lower-priority interrupts
   wrword ip, csp         ; save IP to the stack
   test tmp, #2 wc,wz
   if_c mov ip, #1 wz
   test tmp, #4 wc
   if_c_and_z mov ip, #2 wz
   test tmp, #8 wc
   if_c_and_z mov ip, #3 wz
   test tmp, #16 wc
   if_c_and_z mov ip, #4 wz
   test tmp, #32 wc
   if_c_and_z mov ip, #5 wz
   test tmp, #64 wc
   if_c_and_z mov ip, #6 wz
   if_z mov ip, #7
   shl tmp2, ip         ; shift the mask-mask into place
   shl ip, #2            ; turn the count into a longword index
   add ip, #32            ; and point it to the interupt dispatch table
   andn mask, tmp2         ; mask out lower-priority interrupts
   jmp #doNext
   
   ; 30 free words
   
   res ($140 - $)         ; pad to 0x140
doBranchIfPositive:         ; handle group 0xD (branch-if-positive) opcodes
   test tos, const_8000 wc   ; is TOS negative?
   test tos, const_ffff wz   ; is TOS non-zero?
   shl opcode, #20         ; sign-extend the offset
   asr opcode, #19         ; shift it down to a word offset
   if_nc_and_nz add ip, opcode   ; if TOS is non-negative and non-zero, branch
   rdword tos, dsp         ; bump NOS to TOS
   add dsp, #2            ; and increment DSP
   nop                  ; delay replaces useless instruction
   jmp #doNext
   
   ; 55 free words
   
   res ($180 - $)         ; pad to 0x180
doBranchIfNegative:         ; handle group 0xE (branch-if-negative) opcodes
   test tos, const_8000 wz   ; is TOS negative?
   rdword tos, dsp         ; bump NOS to TOS
   add dsp, #2            ; increment DSP
   shl opcode, #20         ; sign-extend the offset
   asr opcode, #19         ; shift it down to a word offset
   if_nz add ip, opcode   ; if TOS was zero, branch
   rdword tmp, ip         ; delay to align with 0.4 us "cycle time"
   jmp #doNext
   
   ; 56 free words
   
   res ($1c0 - $)         ; pad to 0x1C0
doBranch:               ; handle group 0xF (branch-always) opcodes
   shl opcode, #20         ; sign-extend the offset
   asr opcode, #19         ; shift it down to a word offset
   add ip, opcode         ; branch
   jmp #doNext
   
   ; 44 free words

_________________
"'Legacy code' often differs from its suggested alternative by actually working and scaling." - Bjarne Stroustrup


Wed Oct 23, 2019 4:55 pm WWW

Joined: Wed Oct 23, 2019 1:52 am
Posts: 9
Location: Sacramento, CA
Update: progress on this has been slow, I'm afraid, as I've been busy with several other projects and general life stuff. However, I did end up reworking the design a bit, replacing the push-immediate opcode range with a jump-to-subroutine immediate range (where every even opcode is a call to that address) and reshuffling the opcode format accordingly. This makes subroutine-threaded code compact and simple, which is nice for Forth purposes - most word definitions will consist almost entirely of bare addresses.

Memory addressing got revised a bit as well - adding zero-indexed and stack-indexed addressing (with unsigned displacements) to the (signed) IP-relative and TOS-relative modes; this added flexibility should help make up for the loss of immediate constants. The port-mapped I/O scheme has also been discarded in favor of memory-mapped.

I haven't tried updating the Propeller implementation to reflect this yet as I'm more interested in/focused on the idea of doing it in discrete logic. I have, however, almost finished writing a simulator in which I can try writing some code for this beast and see how I like it. The simulator core is pretty well done; I'm just working on the interface at this point.

I've also put up an official-ish not-very-good-ish page at: http://commodorejohn.freeshell.org/

_________________
"'Legacy code' often differs from its suggested alternative by actually working and scaling." - Bjarne Stroustrup


Tue Nov 02, 2021 4:27 pm WWW

Joined: Mon Oct 07, 2019 2:41 am
Posts: 649
On the other side of the coin, you have something like a Tiny Pascal as stack machine.
Small C is almost stack design, being a simple recursive decent compiler.
Here a compact encoding is needed as the compilers are very brain dead.
Code:
B=C+1;
 lea B
 push B
 ld C
 push C
 ld #1
 push 1
 add
 store **(tos++) = *(tos++)
 A real compiler NON RISC
 LD C
 ADD #1
 ST B


Thu Nov 04, 2021 6:41 am

Joined: Wed Oct 23, 2019 1:52 am
Posts: 9
Location: Sacramento, CA
Signs of life!
Code:
[stackcpu] ./a.out
stacksim - 16-bit stack CPU simulator. (C)2021 commodorejohn.
Q to quit, H for help.

. hello.core load
. 66 0 dis
@000000: @010405    000100 @Z LDW
@000002: @020003    DUP
@000004: @060005    000000 @TOS LDB
@000006: @020003    DUP
@000010: @100141    000030 BEQ
@000012: @015007    001200 @Z STW
@000014: @001421    INC -1 AND
@000016: @177717    177762 BRA
@000020: @000000    000000 JSR
@000022: @000000    000000 JSR
@000024: @000000    000000 JSR
@000026: @000000    000000 JSR
@000030: @000000    000000 JSR
@000032: @000000    000000 JSR
@000034: @000000    000000 JSR
@000036: @000000    000000 JSR
@000040: @010003    DRP
@000042: @010003    DRP
@000044: @040003    BRK
@000046: @000000    000000 JSR
@000050: @000000    000000 JSR
@000052: @000000    000000 JSR
@000054: @000000    000000 JSR
@000056: @000000    000000 JSR
@000060: @062510    062510 JSR
@000062: @066154    066154 JSR
@000064: @026157    000615 @SP STB
@000066: @073440    073440 JSR
@000070: @071157    000232 @TOS STW
@000072: @062154    062154 JSR
@000074: @000041    PCP NOS ADD
@000076: @000000    000000 JSR
@000100: @000060    000060 JSR
. 0 go
Hello, world!
Interrupt raised.

IP: @000044  DSP: @000576  CSP: @000700  MSK: @177776  CB: @0
.

_________________
"'Legacy code' often differs from its suggested alternative by actually working and scaling." - Bjarne Stroustrup


Mon Nov 22, 2021 4:42 pm WWW

Joined: Wed Jan 09, 2013 6:54 pm
Posts: 1799
A milestone - well done!


Mon Nov 22, 2021 4:53 pm

Joined: Wed Oct 23, 2019 1:52 am
Posts: 9
Location: Sacramento, CA
And, after a whole bunch of cleanup, correction, and commenting, here's the source for the initial version of the simulator. It's as complete as the architecture is at the moment (among other things, the details of interrupt handling have yet to be defined, so the BRK instruction just halts the simulation.) Released under the "do whatever" license ;)
Attachment:
simulator.c


You do not have the required permissions to view the files attached to this post.

_________________
"'Legacy code' often differs from its suggested alternative by actually working and scaling." - Bjarne Stroustrup


Fri Dec 10, 2021 1:52 am WWW

Joined: Wed Jan 09, 2013 6:54 pm
Posts: 1799
Well done, and thanks for sharing!


Fri Dec 10, 2021 7:45 am

Joined: Wed Oct 23, 2019 1:52 am
Posts: 9
Location: Sacramento, CA
Update: I've more or less worked out how traps/interrupts will work, and revamped the instruction-set documentation on the project page into a nice clean table format that should make things a bit clearer. The simulator is currently undergoing a major overhaul, partly to implement interrupts, but mostly to add non-blocking I/O* on *nix (and eventually Windows) hosts, so that the simulation can run in "real time." UART I/O is pretty nearly done, after much banging of the head against termios and direct *nix file I/O functions; next I get to bang my head against asynchronous file access for the disk image, oh boy :/

* (Somehow I've been doing hobbyist programming in C for upwards of fifteen years and I've never had the occasion, until now, to discover that plain ANSI C just doesn't offer any means at all to distinguish "no more data" from "no more data currently." Really, K&R? Really?)

Also, because I never have just one project when I can have two or three projects, I've started sketching out a 32-bit extension to the architecture ;D I like to think that, if this is my retro-'70s minicomputer design, however many years down the line I get around to learning FPGAs, that'll be my retro-'90s pizzabox workstation design ;)

_________________
"'Legacy code' often differs from its suggested alternative by actually working and scaling." - Bjarne Stroustrup


Mon Jan 17, 2022 2:01 am WWW
 [ 11 posts ] 

Who is online

Users browsing this forum: CCBot and 0 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

Jump to:  
Powered by phpBB® Forum Software © phpBB Group
Designed by ST Software