Skip to content

Commit

Permalink
Merge pull request #25 from yaacov/stack-ops
Browse files Browse the repository at this point in the history
Add stack opcodes
  • Loading branch information
yaacov authored Oct 3, 2023
2 parents 211e65a + 0fef39b commit 4d5481e
Show file tree
Hide file tree
Showing 11 changed files with 227 additions and 13 deletions.
80 changes: 73 additions & 7 deletions README.ASM.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,23 @@ SMART Assembly is a simple assembly language designed for educational purposes.

Here's a list of available opcodes in the SMART Assembly language:

- `NOP`: No operation, move the program counter.
### Register operations

- `LOADA <label>`: Load the value from the memory location specified by `<label>` into register A.
- `LOADB <label>`: Load the value from the memory location specified by `<label>` into register B.
- `STOREA <label>`: Store the value from register A into the memory location specified by `<label>`.
- `STOREB <label>`: Store the value from register B into the memory location specified by `<label>`.
- `ADDA <label>`: Add the value from the memory location specified by `<label>` to register A.
- `ADDB <label>`: Add the value from the memory location specified by `<label>` to register B.

### Flow Commands

- `NOP`: No operation, move the program counter.
- `JUMP <label>`: Jump to the address specified by `<label>`.
- `JZA <label>`: Jump to the address specified by `<label>` if register A is zero.
- `JZB <label>`: Jump to the address specified by `<label>` if register B is zero.
- `END`: End the program execution.

### Logic Commands

- `ORA <label>`: Perform a bitwise OR operation between register A and the value at `<label>`, storing the result in register A.
- `ORB <label>`: Perform a bitwise OR operation between register B and the value at `<label>`, storing the result in register B.
- `ANDA <label>`: Perform a bitwise AND operation between register A and the value at `<label>`, storing the result in register A.
Expand All @@ -35,15 +45,25 @@ Here's a list of available opcodes in the SMART Assembly language:
- `XORB <label>`: Perform a bitwise XOR operation between register B and the value at `<label>`, storing the result in register B.
- `NOTA`: Perform a bitwise NOT operation on register A.
- `NOTB`: Perform a bitwise NOT operation on register B.

### Arithmetic Commands

- `ADDA <label>`: Add the value from the memory location specified by `<label>` to register A.
- `ADDB <label>`: Add the value from the memory location specified by `<label>` to register B.
- `SHLA <value>`: Shift the bits in register A to the left by `<value>` positions.
- `SHLB <value>`: Shift the bits in register B to the left by `<value>` positions.
- `SHRA <value>`: Shift the bits in register A to the right by `<value>` positions.
- `SHRB <value>`: Shift the bits in register B to the right by `<value>` positions.
- `JUMP <label>`: Jump to the address specified by `<label>`.
- `JZA <label>`: Jump to the address specified by `<label>` if register A is zero.
- `JZB <label>`: Jump to the address specified by `<label>` if register B is zero.
- `END`: End the program execution.

### Stack Commands

- `PUSH <label>`: Push the value from a memory location onto the stack.
- `POP <label>`: Pop the top value from the stack into a memory location.
- `CALL <label>`: Call a subroutine.
- `RET`: Return from a subroutine.
- `SETBP <label>`: Set the Base Pointer for stack operations.
- `LOADA [BP + <value>]`: Load a value from the stack into register A using the Base Pointer and offset.
- `STOREA [BP + <value>]`: Store a value from register A onto the stack using the Base Pointer and offset.

## Comments and Labels

Expand All @@ -70,6 +90,33 @@ START: ; This is a label named START
LOADA NUM1 ; Use the label NUM1 as an operand
```

## Stack opration

The stack is a crucial data structure in SMART Assembly, facilitating temporary storage, parameter passing, and return address handling during subroutine calls. It operates on a Last-In-First-Out (LIFO) principle, where the last value pushed onto the stack is the first to be popped off.

Before utilizing stack operations, it's imperative to set the stack's start address using the SETBP opcode. If SETBP is not used, the stack pointer is set to zero, which could lead to unwanted behavior.

```assembly
SETBP STACK
; rest of your program code;
; ...
; start of stack memory
STACK:
; make sure to leave this memory empty for the stack to grow into it
```

The stack is particularly useful for:

- Parameter Passing: Parameters can be pushed onto the stack before a subroutine call, and then accessed within the subroutine using the LOADABP and STOREABP opcodes.
- Return Address Handling: When a subroutine is called using the CALL opcode, the return address is automatically pushed onto the stack, to be retrieved later by the RET opcode.
- Local Variable Storage: Local variables can be stored on the stack, providing temporary data storage that is cleaned up when the subroutine exits.
- Nested and Recursive Calls: The stack allows for nested and recursive subroutine calls, each with its own set of parameters, local variables, and return address.

See [recursive-fact.asm](./examples/recursive-fact.asm) code for function call example.

## Writing a Simple Program

Let's write a simple program to add two numbers:
Expand All @@ -89,6 +136,25 @@ NUM2: DATA 0x03 ; 3 in decimal
RESULT: DATA 0x00 ; Placeholder for the result
```

This example uses the stack to swap two numbers:

```assembly
SETBP STACK ; Init the stack pointer (base pointer)
; Push the pop to swap NUM1 with NUM2
PUSH NUM1
PUSH NUM2
POP NUM1
POP NUM2
END ; End program
NUM1: DATA 0xF0
NUM2: DATA 0x0F
STACK: ; start of stack memory
```
In this program, we first load the first number into register A. We then add the second number to register A and store the result.

Foe more examples see [examples](./examples/).
Expand Down
47 changes: 47 additions & 0 deletions examples/recursive-fact.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
; Program to calculate factorial revursively

; Init the stack pointer (base pointer)
SETBP STACK

; Call recursive factorial method
PUSH NUM1
CALL FUNC
POP TEMP ; clear stack

; Move result from register B to memory
STOREB RESULT

; End program
END

NUM1: DATA 0x06
RESULT: DATA 0x00

ZERO: DATA 0x00
MINUSONE: DATA 0xFF
TEMP: DATA 0x00

; Recursive factorial
FUNC:
; Load argument to register A
LOADA [BP + 1] ; [base pointer + 1] is the first function argument

; Add the number to register B
STOREA TEMP
ADDB TEMP

; Use register A to check for termination condition (arg == 0)
JZA RET

; Put (arg - 1) in temp
ADDA MINUSONE
STOREA TEMP

; Call recursively with num - 1
PUSH TEMP
CALL FUNC
POP TEMP ; clear stack
RET: RET

; Start of stack
STACK:
21 changes: 21 additions & 0 deletions examples/swap-with-stack.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
; Program to swap two numbers using the stack

; Init the stack pointer (base pointer)
SETBP STACK

; Push two numbers in order
PUSH NUM1
PUSH NUM2

; Pop the numbers out of order
POP NUM1
POP NUM2

; End program
END

NUM1: DATA 0xF0
NUM2: DATA 0x0F

; start of stack memory
STACK:
2 changes: 1 addition & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
</div>

<p class="title-text">Registers</p>
<led-register-table rega="0" regb="0" id="registers-table"></led-register-table>
<led-register-table rega="0" regb="0" sp="0" id="registers-table"></led-register-table>

<p class="title-text">Memory</p>
<led-memory-table class="ledTable" color="red" id="memory-table"></led-memory-table>
Expand Down
5 changes: 3 additions & 2 deletions public/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function updateVmView() {

data.push({
address: i,
indicator: vm.pc === i,
indicator: vm.pc === i || vm.sp === i,
value: vm.memory[i],
label: mapping ? mapping.label : undefined,
opCode: mapping ? mapping.asmOpcode : undefined,
Expand All @@ -61,6 +61,7 @@ function updateVmView() {

registersTable.setAttribute('rega', vm.registerA);
registersTable.setAttribute('regb', vm.registerB);
registersTable.setAttribute('sp', vm.sp);

memoryTable.setAttribute('data', JSON.stringify(data));
}
Expand Down Expand Up @@ -228,7 +229,7 @@ function app() {
// Init editor with insperational quate
codeEditor.code = '; your code here\n\n'
+ '; Select one of the examples on the right,\n'
+ '; or craft your own awesome code.\n'
+ '; or craft your own awesome code.\n\n'
+ '; for more information:\n'
+ '; https://github.com/yaacov/smart-tools/blob/main/README.ASM.md\n';
}
Expand Down
3 changes: 2 additions & 1 deletion public/js/components/code-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ class CodeEditor extends HTMLElement {
highlightText() {
const text = this.editor.innerHTML;
const highlightedText = text
.replace(/(NOP|LOADA|LOADB|STOREA|STOREB|ORA|ORB|ANDA|ANDB|XORA|XORB|NOTA|NOTB|SHLA|SHLB|SHRA|SHRB|JUMP|JZA|JZB|ADDA|ADDB|END|DATA)([^:])/g, '<span class="code">$1</span>$2')
.replace(/(SHRB|JUMP|JZA|JZB|ADDA|ADDB|END|DATA|POP|PUSH|CALL|RET|LOADABP|STOREABP)([^:])/g, '<span class="code">$1</span>$2')
.replace(/(NOP|LOADA|LOADB|STOREA|STOREB|ORA|ORB|ANDA|ANDB|XORA|XORB|NOTA|NOTB|SHLA|SHLB|SHRA)([^:])/g, '<span class="code">$1</span>$2')
.replace(/(0x[\da-fA-F]{2})/g, '<span class="literal">$1</span>')
.replace(/(^|<br>)(\s*\w+:)/g, '$1<span class="label">$2</span>')
.replace(/(;.*?)(<br>|$)/gm, (_, p1, p2) => {
Expand Down
10 changes: 8 additions & 2 deletions public/js/components/led-register-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@ class LedRegisterTable extends HTMLElement {
}

static get observedAttributes() {
return ['rega', 'regb'];
return ['rega', 'regb', 'sp'];
}

attributeChangedCallback(name) {
if (name === 'rega' || name === 'regb') {
if (name === 'rega' || name === 'regb' || name === 'sp') {
this.render();
}
}

render() {
const regA = parseInt(this.getAttribute('rega'), 10);
const regB = parseInt(this.getAttribute('regb'), 10);
const SP = parseInt(this.getAttribute('sp'), 10);

let tableContent = html`
<style>
Expand Down Expand Up @@ -63,6 +64,11 @@ class LedRegisterTable extends HTMLElement {
<td>0x${regB.toString(16).toUpperCase().padStart(2, '0')}</td>
<td class="decimal hidden-on-small">${regB.toString(10).padStart(3, '0')}</td>
</tr>
<td class="label">SP</td>
<td><led-array color="red" width="8" value="${SP}"></led-array></td>
<td>0x${SP.toString(16).toUpperCase().padStart(2, '0')}</td>
<td class="decimal hidden-on-small">${SP.toString(10).padStart(3, '0')}</td>
</tr>
</table>
`;

Expand Down
22 changes: 22 additions & 0 deletions src/compiler.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,27 @@ function removeComments(line) {
return line.trim();
}

/**
* Replaces instances of "STOREA [BP + <number>]" with "STOREABP <number>"
* and "LOADA [BP + <number>]" with "LOADABP <number>" in a given line.
*
* @param {string} line - The input line of text.
* @returns {string} - The modified line of text with the desired replacements.
*/
function replaceSyntax(line) {
// The regex pattern for STOREA transformation
const storeaPattern = /STOREA\s*\[\s*BP\s*\+\s*(\d+)\s*\]/g;
const storeaReplacement = 'STOREABP $1';
const modifiedStoreaLine = line.replace(storeaPattern, storeaReplacement);

// The regex pattern for LOADA transformation
const loadaPattern = /LOADA\s*\[\s*BP\s*\+\s*(\d+)\s*\]/g;
const loadaReplacement = 'LOADABP $1';
const modifiedLine = modifiedStoreaLine.replace(loadaPattern, loadaReplacement);

return modifiedLine;
}

/**
* Ensures there's a space after a label colon in a line of assembly code.
* @param {string} line - A line of assembly code.
Expand Down Expand Up @@ -122,6 +143,7 @@ function processInstruction(instruction, operands, memory, labels, memoryMapping
function compile(assemblyTxt) {
const assemblyLines = assemblyTxt.split('\n')
.map(removeComments)
.map(replaceSyntax)
.filter((line) => line.trim().length > 0)
.map(ensureSpaceAfterLabel);
const memory = [];
Expand Down
7 changes: 7 additions & 0 deletions src/disassemble.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ export function disassemble(memory) {
case 'XORB':
case 'ADDA':
case 'ADDB':
case 'PUSH':
case 'POP':
case 'CALL':
case 'SETBP':
case 'LOADABP':
case 'STOREABP':
operandValue = memory[++address];
if (!labels[operandValue]) {
labels[operandValue] = `DAT${labelDatIndex++}`;
Expand All @@ -87,6 +93,7 @@ export function disassemble(memory) {
case 'ORA':
case 'ORB':
case 'NOP':
case 'RET':
// obCodes with no operands
break;
case 'END':
Expand Down
14 changes: 14 additions & 0 deletions src/opcodes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ export const opcodes = {
JZB: 0x13,
ADDA: 0x14,
ADDB: 0x15,
PUSH: 0x17,
POP: 0x18,
CALL: 0x19,
RET: 0x1A,
SETBP: 0x1B,
LOADABP: 0x1C,
STOREABP: 0x1D,
END: 0xFF,
};

Expand All @@ -47,6 +54,13 @@ export const opcodesParams = {
[opcodes.JZB]: 1,
[opcodes.ADDA]: 1,
[opcodes.ADDB]: 1,
[opcodes.PUSH]: 1,
[opcodes.POP]: 1,
[opcodes.CALL]: 1,
[opcodes.RET]: 0,
[opcodes.SETBP]: 1,
[opcodes.LOADABP]: 1,
[opcodes.STOREABP]: 1,
[opcodes.END]: 0,
};

Expand Down
29 changes: 29 additions & 0 deletions src/vm.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,25 @@ class VM {
this.registerA = 0;
this.registerB = 0;
this.pc = 0;
this.sp = 0;
}

fetchOperand() {
return this.memory[this.pc + 1];
}

pop() {
const value = this.memory[this.sp];
this.sp -= 1;

return value;
}

push(value) {
this.sp += 1;
this.memory[this.sp] = value;
}

/**
* Executes the current instruction in memory.
*
Expand Down Expand Up @@ -45,6 +58,22 @@ class VM {
[opcodes.JZB]: () => { const address = this.fetchOperand(); if (this.registerB === 0) { this.pc = address; jumpFlag = true; } },
[opcodes.ADDA]: () => { this.registerA = (this.registerA + this.memory[this.fetchOperand()]) & 0xff; },
[opcodes.ADDB]: () => { this.registerB = (this.registerB + this.memory[this.fetchOperand()]) & 0xff; },
[opcodes.SETBP]: () => { const address = this.fetchOperand(); this.sp = address; },
[opcodes.PUSH]: () => { const value = this.memory[this.fetchOperand()]; this.push(value); },
[opcodes.POP]: () => { this.memory[this.fetchOperand()] = this.pop(); },
[opcodes.CALL]: () => {
this.push(this.pc + 2); /* +2 = code + operand */
const address = this.fetchOperand(); this.pc = address; jumpFlag = true;
},
[opcodes.RET]: () => { this.pc = this.pop(); jumpFlag = true; },
[opcodes.LOADABP]: () => {
const address = (this.sp - this.fetchOperand()) & 0xff;
this.registerA = this.memory[address];
},
[opcodes.STOREABP]: () => {
const address = (this.sp - this.fetchOperand()) & 0xff;
this.memory[address] = this.registerA;
},
[opcodes.END]: () => false,
};

Expand Down

0 comments on commit 4d5481e

Please sign in to comment.