's Avatar

@kissa

Tomfoolery, nincompoopery, programming

82
Followers
646
Following
40
Posts
13.10.2023
Joined
Posts Following

Latest posts by @kissa

For sale: baby jetpack. Worn once

09.01.2025 23:24 πŸ‘ 963 πŸ” 189 πŸ’¬ 10 πŸ“Œ 5

It's been a while, maybe time to get back to computer and write something

30.12.2024 19:12 πŸ‘ 1 πŸ” 0 πŸ’¬ 0 πŸ“Œ 0

That’s why it would only affect your followers β€” don’t follow people who broadcast wrong signals.

I think subtle negative signals would be more useful than generally imagined, but textual medias rarely use them. TikTok can detect when people skip videos, but it’s harder in these Twitter clones

13.11.2023 07:47 πŸ‘ 0 πŸ” 0 πŸ’¬ 0 πŸ“Œ 0

What if we made a social network where instead of liking, blocking and muting, you would β€œhush” and β€œboost” posts?

Hushing would decrease visibility to your followers. Boosting would increase it.

Who’s with me?

13.11.2023 07:31 πŸ‘ 3 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0
Tweet by @heydonworks that sparked some controversy in 2019:

What do you like to do when you're not coding?
Vue-based developer: I am fond of cooking, getting stuck into a good graphic novel andβ€”
React-based developer: WEIGHTS. BROING DOWN. MORE WEIGHTS. TRUMP. WEIGHTS. GUNS. THE FREE MARKET.

Tweet by @heydonworks that sparked some controversy in 2019: What do you like to do when you're not coding? Vue-based developer: I am fond of cooking, getting stuck into a good graphic novel andβ€” React-based developer: WEIGHTS. BROING DOWN. MORE WEIGHTS. TRUMP. WEIGHTS. GUNS. THE FREE MARKET.

People in Bluesky vs. people in Place Previously Known As Twitter

07.11.2023 15:50 πŸ‘ 3 πŸ” 0 πŸ’¬ 0 πŸ“Œ 0

β€œCan you explain this gap in your resume?”

Yes, child; that was when I carried you

05.11.2023 21:24 πŸ‘ 10 πŸ” 2 πŸ’¬ 0 πŸ“Œ 0

Thank you! Kissa is a many-splendored thing

01.11.2023 23:42 πŸ‘ 0 πŸ” 0 πŸ’¬ 0 πŸ“Œ 0

Now I need to take a break of few days, weeks, or months, depending on when I next manage to free up some time. This was a miserable week for writing code: I had planned 5 full working days and got some 11+ hours done. But such is life sometimes

19.10.2023 20:16 πŸ‘ 0 πŸ” 0 πŸ’¬ 0 πŸ“Œ 0
List of test instructions that ChatGPT wrote me.

export const opcodes: { [key: number]: string } = {
	0b00000000: "add", // add <op1> <op2> <op3>  - op1 <- op2 + op3
	0b00000001: "sub", // sub <op1> <op2> <op3>  - op1 <- op2 - op3
	0b00000010: "mul", // mul <op1> <op2> <op3>  - op1 <- op2 * op3
	// etc, ending with 0b00001100: "mov"
	
	0b00010000: "adda", // add <op1> <op2>       - op1 <- op1 + op2
	0b00010001: "suba", // sub <op1> <op2>       - op1 <- op1 - op2
	0b00010010: "mula", // mul <op1> <op2>       - op1 <- op1 * op2
	// etc, ending with 0b00011100: "nop"
	
	0b11110000: "hlt", // hlt <?> <?> <?>         - halt program execution
	0b11110001: "hcf", // hcf <?> <?> <?>         - halt and catch fire
}

List of test instructions that ChatGPT wrote me. export const opcodes: { [key: number]: string } = { 0b00000000: "add", // add <op1> <op2> <op3> - op1 <- op2 + op3 0b00000001: "sub", // sub <op1> <op2> <op3> - op1 <- op2 - op3 0b00000010: "mul", // mul <op1> <op2> <op3> - op1 <- op2 * op3 // etc, ending with 0b00001100: "mov" 0b00010000: "adda", // add <op1> <op2> - op1 <- op1 + op2 0b00010001: "suba", // sub <op1> <op2> - op1 <- op1 - op2 0b00010010: "mula", // mul <op1> <op2> - op1 <- op1 * op2 // etc, ending with 0b00011100: "nop" 0b11110000: "hlt", // hlt <?> <?> <?> - halt program execution 0b11110001: "hcf", // hcf <?> <?> <?> - halt and catch fire }

Implementation of first 3 arithmetic instructions:

	if (instr === "add") {
		writeOperand(op1, op2value + op3value)
	}
	if (instr === "sub") {
		writeOperand(op1, op2value - op3value)
	}
	if (instr === "mul") {
		writeOperand(op1, op2value * op3value)
	}
	if (instr === "hlt") {
		return false
	}
	if (instr === "hcf") {
		error("Machine caught fire")
	}
	return true

Implementation of first 3 arithmetic instructions: if (instr === "add") { writeOperand(op1, op2value + op3value) } if (instr === "sub") { writeOperand(op1, op2value - op3value) } if (instr === "mul") { writeOperand(op1, op2value * op3value) } if (instr === "hlt") { return false } if (instr === "hcf") { error("Machine caught fire") } return true

test("arithmetic instructions", () => {
	testArithmetic(instr.add, 0x1234, 0x2345, 0x3579)
	testArithmetic(instr.sub, 0x4131, 0x2121, 0x2010)
	testArithmetic(instr.mul, 111, 20, 2220)
	testArithmetic(instr.mul, -111, 20, -2220)
	testArithmetic(instr.mul, -1, -0x7fff, 0x7fff)
	// TODO should overflow probably
	// testArithmetic(instr.mul, -1, -0x8000, -0x8000)
})

test("arithmetic instructions", () => { testArithmetic(instr.add, 0x1234, 0x2345, 0x3579) testArithmetic(instr.sub, 0x4131, 0x2121, 0x2010) testArithmetic(instr.mul, 111, 20, 2220) testArithmetic(instr.mul, -111, 20, -2220) testArithmetic(instr.mul, -1, -0x7fff, 0x7fff) // TODO should overflow probably // testArithmetic(instr.mul, -1, -0x8000, -0x8000) })

Actually need to implement a bunch of arithmetic instructions and test them somehow efficiently. Here's a start.

19.10.2023 20:15 πŸ‘ 1 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0
Previous picture, plus new stuff (8-bit immediate instructions)

// If bit 6 (immediate mode) is set, bit 7 sets "byte mode"
//
// Instruction names are postfixed with "b" for immediate.
// Arithmetic instructions with 8-bit immediate operand (96-111):
//
// addb op1, op2, imm8 ; op1 <- op2 + imm8     01100000
// subb
// mulb
// divb
// modb
// andb
// orb
// xorb
// shlb
// shrb
// add1b
// sub1b
// movb
//
// // Arithmetic instructions with 8-bit immediate operand, augmented assignment
// (112-127):
//
// addab op1, imm8     ; op1 <- op2 + imm8     01110000
// subab
// mulab
// divab
// modab
// andab
// orab
// xorab
// shlab
// shrab
// incab
// decab
// nopab

Previous picture, plus new stuff (8-bit immediate instructions) // If bit 6 (immediate mode) is set, bit 7 sets "byte mode" // // Instruction names are postfixed with "b" for immediate. // Arithmetic instructions with 8-bit immediate operand (96-111): // // addb op1, op2, imm8 ; op1 <- op2 + imm8 01100000 // subb // mulb // divb // modb // andb // orb // xorb // shlb // shrb // add1b // sub1b // movb // // // Arithmetic instructions with 8-bit immediate operand, augmented assignment // (112-127): // // addab op1, imm8 ; op1 <- op2 + imm8 01110000 // subab // mulab // divab // modab // andab // orab // xorab // shlab // shrab // incab // decab // nopab

Here's the whole set of immediate instructions. This makes me reconsider the design, not only because of the effort of testing all of that stuff but also because I don't want to type "SHLAB" when I just want to do a 1-bit left shift. Maybe it's time to write some programs first and reconsider this.

19.10.2023 09:11 πŸ‘ 0 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0

I'm deliberately making a 1:1 mapping between names and opcodes to make assembling and disassembling easier. I may have to walk back on this decision later. It makes more complex names for often used instructions:

addai a, 1234 ; a: augmented assignment, b: immediate
addab a, 10 ; b: byte immediate

19.10.2023 08:23 πŸ‘ 0 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0
Immediate instructions with 16-bit operand (32-47), plus augmented ones (48-63)

(Details omitted due to space constraints)

addi op1, op2, imm16 ; op1 <- op2 + imm16   00100000
subi
muli
divi
modi
andi
ori
xori
shli
shri
add1i
sub1i
movi

addai op1, imm16     ; op1 <- op2 + imm16   00110000
subai
mulai
divai
modai
andai
orai
xorai
shlai
shrai
incai
decai
nopai

Immediate instructions with 16-bit operand (32-47), plus augmented ones (48-63) (Details omitted due to space constraints) addi op1, op2, imm16 ; op1 <- op2 + imm16 00100000 subi muli divi modi andi ori xori shli shri add1i sub1i movi addai op1, imm16 ; op1 <- op2 + imm16 00110000 subai mulai divai modai andai orai xorai shlai shrai incai decai nopai

Good two hours reserved for today, let's continue with immediate instructions. Since we have augmented mode too, there's quite a bunch of these.

Instructions that logically take only 1 argument don't make sense, such as INC/DEC, and some potential ones like NEG, NOT, CLR. Except for MOV.

19.10.2023 08:08 πŸ‘ 3 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0

I'm starting to think that maybe augmented assignment forms should be the default and the RISC-style 3 operand forms be the exception, since we want as short programs as possible. And generally shorter mnemonics as well. Maybe in the next version!

18.10.2023 13:09 πŸ‘ 0 πŸ” 0 πŸ’¬ 0 πŸ“Œ 0

Augmented assignment instructions are derived from regular ones by appending "a" to the name, except for inc and dec (derived from add1 and sub1), and nop (derived from mov). Most instructions take 1 fewer parameter than the original one; nop, exceptionally, takes zero, for obvious reasons.

18.10.2023 13:03 πŸ‘ 0 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0
//
// Augmented arithmetic instructions (16-31)
//
// adda op1, op2       ; op1 <- op2 + op3      00010000
// suba op1, op2       ; op1 <- op2 - op3      00010001
// mula op1, op2       ; op1 <- op2 * op3      00010010
// diva op1, op2       ; op1 <- op2 / op3      00010011
// moda op1, op2       ; op1 <- op2 % op3      00010100
// anda op1, op2       ; op1 <- op2 & op3      00010101
// ora op1, op2        ; op1 <- op2 | op3      00010110
// xora op1, op2       ; op1 <- op2 ^ op3      00010111
// shla op1, op2       ; op1 <- op2 << op3     00011000
// shra op1, op2       ; op1 <- op2 >> op3     00011001
// inc op1             ; op1 <- op2 + 1        00011010 - Note name
// dec op1             ; op1 <- op2 - 1        00011011 - Note name
// nop                 ; (do nothing)          00011100 - Note zero operands
//
// Augmented add1 is called inc. Augmented sub1 is called dec. Augmented mov
// is called nop and takes 0 parameters.
//

// // Augmented arithmetic instructions (16-31) // // adda op1, op2 ; op1 <- op2 + op3 00010000 // suba op1, op2 ; op1 <- op2 - op3 00010001 // mula op1, op2 ; op1 <- op2 * op3 00010010 // diva op1, op2 ; op1 <- op2 / op3 00010011 // moda op1, op2 ; op1 <- op2 % op3 00010100 // anda op1, op2 ; op1 <- op2 & op3 00010101 // ora op1, op2 ; op1 <- op2 | op3 00010110 // xora op1, op2 ; op1 <- op2 ^ op3 00010111 // shla op1, op2 ; op1 <- op2 << op3 00011000 // shra op1, op2 ; op1 <- op2 >> op3 00011001 // inc op1 ; op1 <- op2 + 1 00011010 - Note name // dec op1 ; op1 <- op2 - 1 00011011 - Note name // nop ; (do nothing) 00011100 - Note zero operands // // Augmented add1 is called inc. Augmented sub1 is called dec. Augmented mov // is called nop and takes 0 parameters. //

Next, we want "augmented assignment" forms of those instructions (such as "a += b"), and then also immediate forms taking either a 8-bit or a 16-bit operand. The complexity is quickly getting out of hand. But let's start with the augmented ones:

18.10.2023 12:57 πŸ‘ 0 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0

Notably missing: NOT (toggle all bits) and CLR. Though NOT is popular in many architectures, it's rarely used even in the kind of low-level programming I have in mind. CLR might find itself in the augmented instruction block (coming soon). Also absent: ROL and ROR, which might be fun for some uses.

18.10.2023 12:39 πŸ‘ 0 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0
Instructions

Arithmetic instructions (0-15)

add op1, op2, op3   ; op1 <- op2 + op3    00000000
sub op1, op2, op3   ; op1 <- op2 - op3    00000001
mul op1, op2, op3   ; op1 <- op2 * op3    00000010
div op1, op2, op3   ; op1 <- op2 / op3    00000011
mod op1, op2, op3   ; op1 <- op2 % op3    00000100
and op1, op2, op3   ; op1 <- op2 & op3    00000101
or op1, op2, op3    ; op1 <- op2 | op3    00000110
xor op1, op2, op3   ; op1 <- op2 ^ op3    00000111
shl op1, op2, op3   ; op1 <- op2 << op3   00001000
shr op1, op2, op3   ; op1 <- op2 >> op3   00001001
add1 op1, op2       ; op1 <- op2 + 1      00001010
sub1 op1, op2       ; op1 <- op2 - 1      00001011
mov op1, op2        ; op1 <- op2          00001100

Arithmetic instructions ending in 1101, 1110, 1111 are reserved. Some
candidates:

abs op1, op2        ; op1 <- abs(op2)
sgn op1, op2        ; op1 <- sign(op2)
neg op1, op2        ; op1 <- -op2
add2 op1, op2       ; op1 <- op2 + 2
sub2 op1, op2       ; op1 <- op2 - 2

Instructions Arithmetic instructions (0-15) add op1, op2, op3 ; op1 <- op2 + op3 00000000 sub op1, op2, op3 ; op1 <- op2 - op3 00000001 mul op1, op2, op3 ; op1 <- op2 * op3 00000010 div op1, op2, op3 ; op1 <- op2 / op3 00000011 mod op1, op2, op3 ; op1 <- op2 % op3 00000100 and op1, op2, op3 ; op1 <- op2 & op3 00000101 or op1, op2, op3 ; op1 <- op2 | op3 00000110 xor op1, op2, op3 ; op1 <- op2 ^ op3 00000111 shl op1, op2, op3 ; op1 <- op2 << op3 00001000 shr op1, op2, op3 ; op1 <- op2 >> op3 00001001 add1 op1, op2 ; op1 <- op2 + 1 00001010 sub1 op1, op2 ; op1 <- op2 - 1 00001011 mov op1, op2 ; op1 <- op2 00001100 Arithmetic instructions ending in 1101, 1110, 1111 are reserved. Some candidates: abs op1, op2 ; op1 <- abs(op2) sgn op1, op2 ; op1 <- sign(op2) neg op1, op2 ; op1 <- -op2 add2 op1, op2 ; op1 <- op2 + 2 sub2 op1, op2 ; op1 <- op2 - 2

Got a cup of coffee and made a plan for arithmetic instructions. Note the absence of unsigned operators like logical shift right, because who needs those when all words are signed?

Note that mov is considered one of the arithmetic opcodes (it's similar in structure to e.g. neg, just does nothing)

18.10.2023 12:26 πŸ‘ 0 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0
export const opcodes: { [key: number]: string } = {
	0b00000000: "add", // add <dst> <src1> <src2> - dst <- src1 + src2
	0b00000001: "sub", // sub <dst> <src1> <src2> - dst <- src1 - src2

	0b11110000: "hlt", // hlt <?> <?> <?>         - halt program execution
	0b11110001: "hcf", // hcf <?> <?> <?>         - halt and catch fire
}

export const opcodes: { [key: number]: string } = { 0b00000000: "add", // add <dst> <src1> <src2> - dst <- src1 + src2 0b00000001: "sub", // sub <dst> <src1> <src2> - dst <- src1 - src2 0b11110000: "hlt", // hlt <?> <?> <?> - halt program execution 0b11110001: "hcf", // hcf <?> <?> <?> - halt and catch fire }

Day 3 and can't decide whether to take a nap or get a coffee. Good 2 hours to go and some code needs written. Maybe should expand this poor opcode list today

18.10.2023 11:01 πŸ‘ 3 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0

Here's the comment from 2022: github.com/nguillaumin/...

The 2002 (2004?) thing: nguillaumin.github.io/perihelion-m...

The 1994 thing can be found here: docs.dev-docs.org/htm/search.p...

17.10.2023 21:27 πŸ‘ 1 πŸ” 0 πŸ’¬ 0 πŸ“Œ 0

In other news, while looking for some m68k wisdom, I found a historical gem where James Ingram, who wrote "The 68000 programmer's introduction to demo techniques" (1994) commented on perihelion's "The Atari ST MC68000 Assembly Language Tutorials" (2002) just one year ago and found his own font there

17.10.2023 21:24 πŸ‘ 0 πŸ” 0 πŸ’¬ 2 πŸ“Œ 0
// Write 8-bit unsigned byte `byte` to memory address `addr`, or `more` after
// that if provided
export function write8(addr: number, byte: number, ...more: Array<number>) {
	if (byte < 0 || byte >= 256) {
		error(`write8: byte doesn't fit ${byte}`)
	}
	if (addr < 0 || addr >= MEM_SIZE) {
		error(`write8: invalid address ${addr}`)
	}

	mem[addr] = byte

	if (more.length) {
		for (let i = 0; i < more.length; i++) {
			mem[addr + i + 1] = more[i]
		}
	}
}

// Write 8-bit unsigned byte `byte` to memory address `addr`, or `more` after // that if provided export function write8(addr: number, byte: number, ...more: Array<number>) { if (byte < 0 || byte >= 256) { error(`write8: byte doesn't fit ${byte}`) } if (addr < 0 || addr >= MEM_SIZE) { error(`write8: invalid address ${addr}`) } mem[addr] = byte if (more.length) { for (let i = 0; i < more.length; i++) { mem[addr + i + 1] = more[i] } } }

test("writing bytes", () => {
	// add a.hi, b, c
	write8(0x100, instr.add, op.a_hi, op.b, op.c)

	// add a.lo, d, x
	write8(0x104, instr.add, op.a_lo, op.d, op.x)

	write8(0x108, instr.hlt)

	// Set initial values to registers and point PC to start
	write(addr.pc, 0x100)
	// Clear registers
	write(addr.a, 0)
	write(addr.b, 0x12) //   12
	write(addr.c, 0x34) // + 34 -> 0x46__
	write(addr.d, 0x56) //   56
	write(addr.x, 0x78) // + 78 -> 0x__ce

	run()

	expect(read(addr.a)).toBe(0x46ce)
})

test("writing bytes", () => { // add a.hi, b, c write8(0x100, instr.add, op.a_hi, op.b, op.c) // add a.lo, d, x write8(0x104, instr.add, op.a_lo, op.d, op.x) write8(0x108, instr.hlt) // Set initial values to registers and point PC to start write(addr.pc, 0x100) // Clear registers write(addr.a, 0) write(addr.b, 0x12) // 12 write(addr.c, 0x34) // + 34 -> 0x46__ write(addr.d, 0x56) // 56 write(addr.x, 0x78) // + 78 -> 0x__ce run() expect(read(addr.a)).toBe(0x46ce) })

Writing tests is more convenient by allowing write8() to take multiple arguments:

17.10.2023 21:12 πŸ‘ 0 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0
Code diff where "instructions." has been changed to "instr.", and registers like "A" to "op.a" when used as operand, but to "addr.a" when used as absolute address

Code diff where "instructions." has been changed to "instr.", and registers like "A" to "op.a" when used as operand, but to "addr.a" when used as absolute address

Let's make things a bit more consistent. Left: before, right: after

17.10.2023 19:51 πŸ‘ 0 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0
export function step(): boolean {
	let pc: number = registers.pc
	log(`step: pc ${print(pc)}`)

	let opcode: number = read8(pc++)
	let instr: string = opcodes[opcode] ?? "nop"

	let op1: number = read8(pc++)
	let op2: number = read8(pc++)
	let op3: number = read8(pc++)

	registers.pc = pc

	let op2value = readOperand(op2)
	let op3value = readOperand(op3)

	log(
		`step: instr ${instr} ${print(opcode)} ${print(op1)} ${print(op2)} ${print(
			op3,
		)}`,
	)

	if (instr === "add") {
		let result = op2value + op3value
		writeOperand(op1, result)
		return true
	}
	if (instr === "hlt") {
		return false
	}
	if (instr === "hcf") {
		error("Machine caught fire")
	}
	return true
}

export function step(): boolean { let pc: number = registers.pc log(`step: pc ${print(pc)}`) let opcode: number = read8(pc++) let instr: string = opcodes[opcode] ?? "nop" let op1: number = read8(pc++) let op2: number = read8(pc++) let op3: number = read8(pc++) registers.pc = pc let op2value = readOperand(op2) let op3value = readOperand(op3) log( `step: instr ${instr} ${print(opcode)} ${print(op1)} ${print(op2)} ${print( op3, )}`, ) if (instr === "add") { let result = op2value + op3value writeOperand(op1, result) return true } if (instr === "hlt") { return false } if (instr === "hcf") { error("Machine caught fire") } return true }

And here is the core of the virtual machine:

17.10.2023 09:31 πŸ‘ 0 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0
function readOperand(op: number) {
	let lowest6bits = op & 0b0011_1111
	if (op & 0b0100_0000) {
		return read8s(lowest6bits)
	} else {
		return read(lowest6bits)
	}
}

function writeOperand(op: number, value: number) {
	let lowest6bits = op & 0b0011_1111
	if (op & 0b0100_0000) {
		write8(lowest6bits, value)
	} else {
		write(lowest6bits, value)
	}
}

function readOperand(op: number) { let lowest6bits = op & 0b0011_1111 if (op & 0b0100_0000) { return read8s(lowest6bits) } else { return read(lowest6bits) } } function writeOperand(op: number, value: number) { let lowest6bits = op & 0b0011_1111 if (op & 0b0100_0000) { write8(lowest6bits, value) } else { write(lowest6bits, value) } }

export function read8s(addr: number) {
	if (addr < 0 || addr >= MEM_SIZE) {
		error(`read8: invalid address ${addr}`)
	}

	let result = mem[addr]

	if ((result & 0b1000_0000) > 0) {
		// Sign-extend
		return result | 0xffffff00
	} else {
		return result
	}
}

export function read8s(addr: number) { if (addr < 0 || addr >= MEM_SIZE) { error(`read8: invalid address ${addr}`) } let result = mem[addr] if ((result & 0b1000_0000) > 0) { // Sign-extend return result | 0xffffff00 } else { return result } }

To read and write bytes we introduce readOperand() and writeOperand(), which are used to do the heavy lifting. We also need a function to read and sign-extend a byte, read8s(). Extra write function is not needed, write8 works for signed bytes as well.

17.10.2023 09:27 πŸ‘ 0 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0
export function hi(op: number): number {
	return op | 0b0100_0000
}

export function lo(op: number): number {
	return (op + 1) | 0b0100_0000
}

export function hi(op: number): number { return op | 0b0100_0000 } export function lo(op: number): number { return (op + 1) | 0b0100_0000 }

hi() and lo() set the 7th bit of the operand, which turns on byte addressing (as opposed to word addressing, which is the default)

17.10.2023 09:18 πŸ‘ 0 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0
test("writing bytes", () => {
	// add a.lo, b, c
	write8(0x100, instructions.add)
	write8(0x101, hi(A))
	write8(0x102, B)
	write8(0x103, C)

	// add a.hi, d, x
	write8(0x104, instructions.add)
	write8(0x105, lo(A))
	write8(0x106, D)
	write8(0x107, X)

	write8(0x108, instructions.hlt)

	// Set initial values to registers and point PC to start
	write(PC, 0x100)
	// Clear registers
	write(A, 0)
	write(B, 0x12) //   12
	write(C, 0x34) // + 34 -> 0x46__
	write(D, 0x56) //   56
	write(X, 0x78) // + 78 -> 0x__ce

	run()

	expect(read(A)).toBe(0x46ce)
})

test("writing bytes", () => { // add a.lo, b, c write8(0x100, instructions.add) write8(0x101, hi(A)) write8(0x102, B) write8(0x103, C) // add a.hi, d, x write8(0x104, instructions.add) write8(0x105, lo(A)) write8(0x106, D) write8(0x107, X) write8(0x108, instructions.hlt) // Set initial values to registers and point PC to start write(PC, 0x100) // Clear registers write(A, 0) write(B, 0x12) // 12 write(C, 0x34) // + 34 -> 0x46__ write(D, 0x56) // 56 write(X, 0x78) // + 78 -> 0x__ce run() expect(read(A)).toBe(0x46ce) })

After a bit of byte wrangling, we can now write and read bytes (which, upon reading, are sign-extended)

17.10.2023 09:13 πŸ‘ 1 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0
βœ“ jump relative with add [0.03ms]
step: pc 0x0100
step: instr add (0x0000)
step: pc 0x0104
step: instr hlt (0x00f0)
βœ“ program with byte addressing [0.08ms]

βœ“ jump relative with add [0.03ms] step: pc 0x0100 step: instr add (0x0000) step: pc 0x0104 step: instr hlt (0x00f0) βœ“ program with byte addressing [0.08ms]

test("program with byte addressing", () => {
	// add a, a, c.hi
	write8(0x100, instructions.add)
	write8(0x101, A)
	write8(0x102, A)
	write8(0x103, hi(C))
	// add b, c, c.lo
	write8(0x104, instructions.add)
	write8(0x105, B)
	write8(0x106, B)
	write8(0x107, lo(C))
	write8(0x104, instructions.hlt)

	// Set initial values to registers and point PC to start
	write(PC, 0x100)
	// C contains high byte 0x1f, and low byte 0xf1
	write(C, 0x1ff1)

	debugOn()
	run()
	debugOff()

	// expect(read(A)).toBe(0x1f)
	// B should be sign-extended
	// expect(read(B)).toBe(0xfff1)
})

test("program with byte addressing", () => { // add a, a, c.hi write8(0x100, instructions.add) write8(0x101, A) write8(0x102, A) write8(0x103, hi(C)) // add b, c, c.lo write8(0x104, instructions.add) write8(0x105, B) write8(0x106, B) write8(0x107, lo(C)) write8(0x104, instructions.hlt) // Set initial values to registers and point PC to start write(PC, 0x100) // C contains high byte 0x1f, and low byte 0xf1 write(C, 0x1ff1) debugOn() run() debugOff() // expect(read(A)).toBe(0x1f) // B should be sign-extended // expect(read(B)).toBe(0xfff1) })

Added some printf debugging machinery to find out why my second add instruction didn't run, can you guess why?

17.10.2023 08:37 πŸ‘ 0 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0
Autumn

Autumn

Day 2 of virtual machine hacking, think I’ll do byte addressing now

17.10.2023 08:19 πŸ‘ 1 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0
Operand format:

Direct addressing (word), values 0..63:
00mmmmmm: [M], where M is number 0-63

Direct addressing (byte), values 64..127:
01mmmmmm: [M].b, where M is number 0-63

Indirect addressing with immediate offset (word), values 128..175:
10ooooaa: [<rega> + O], where <rega> is selected with bits 'aa' and O is
the range 0b0000-0b1011, represented by bits 'oooo', mapped to values -6 to 5

Indirect addressing with index register (word), values 176..191:
1011iiaa: [<rega> + <regi>], where <rega> is selected with bits 'aa' and
<regi> is selected with bits 'ii'

Indirect addressing with immediate offset (byte), values 192..239:
11ooooaa: [<rega> + O].b, where <rega> is selected with bits 'aa' and O is
range 0b0000-0b1011 mapped to values -6 to 5

Indirect addressing with index register (byte), values 240..255:
1111iiaa: [<rega> + <regi>].b, where <rega> is selected with bits 'aa' and
<regi> is selected with bits 'ii'

Operand format: Direct addressing (word), values 0..63: 00mmmmmm: [M], where M is number 0-63 Direct addressing (byte), values 64..127: 01mmmmmm: [M].b, where M is number 0-63 Indirect addressing with immediate offset (word), values 128..175: 10ooooaa: [<rega> + O], where <rega> is selected with bits 'aa' and O is the range 0b0000-0b1011, represented by bits 'oooo', mapped to values -6 to 5 Indirect addressing with index register (word), values 176..191: 1011iiaa: [<rega> + <regi>], where <rega> is selected with bits 'aa' and <regi> is selected with bits 'ii' Indirect addressing with immediate offset (byte), values 192..239: 11ooooaa: [<rega> + O].b, where <rega> is selected with bits 'aa' and O is range 0b0000-0b1011 mapped to values -6 to 5 Indirect addressing with index register (byte), values 240..255: 1111iiaa: [<rega> + <regi>].b, where <rega> is selected with bits 'aa' and <regi> is selected with bits 'ii'

What we have now is direct addressing, e.g. [0x1234]. This extends the design with indirect addressing using either immediate or an index register, allowing things like [pc + 5] and [p + i]. Also byte addressing is supported with syntax [0x1234].b

16.10.2023 13:40 πŸ‘ 0 πŸ” 0 πŸ’¬ 0 πŸ“Œ 0
Syntax

[M] means the word at memory address M.

[M].b means the byte at memory address M. When read, it is sign-extended
to a word.

<reg> is equivalent to [addressof <reg>], where <reg> is one of registers
(a, b, c, d, x, y, i, j, p, q, sp, pc), and addressof <reg> is the memory
address of the register according to the table below.

<reg>.hi is equivalent to [address of <reg>].b
<reg>.lo is equivalent to [address of <reg> + 1].b

Table of registers and their addresses:

a:  0x0    a.hi:  0x0    a.lo:  0x1
b:  0x2    b.hi:  0x2    b.lo:  0x3
c:  0x4    c.hi:  0x4    c.lo:  0x5
d:  0x6    d.hi:  0x6    d.lo:  0x7
x:  0x8    x.hi:  0x8    x.lo:  0x9
y:  0xa    y.hi:  0xa    y.lo:  0xb
i:  0xc    i.hi:  0xc    i.lo:  0xd
j:  0xe    j.hi:  0xe    j.lo:  0xf
p:  0x10   p.hi:  0x10   p.lo:  0x11
q:  0x12   q.hi:  0x12   q.lo:  0x13
sp: 0x14   sp.hi: 0x14   sp.lo: 0x15
pc: 0x16   pc.hi: 0x16   pc.lo: 0x17

Finally:

<regi> is one of x, y, i, j
<rega> is one of p, q, sp, pc

Syntax [M] means the word at memory address M. [M].b means the byte at memory address M. When read, it is sign-extended to a word. <reg> is equivalent to [addressof <reg>], where <reg> is one of registers (a, b, c, d, x, y, i, j, p, q, sp, pc), and addressof <reg> is the memory address of the register according to the table below. <reg>.hi is equivalent to [address of <reg>].b <reg>.lo is equivalent to [address of <reg> + 1].b Table of registers and their addresses: a: 0x0 a.hi: 0x0 a.lo: 0x1 b: 0x2 b.hi: 0x2 b.lo: 0x3 c: 0x4 c.hi: 0x4 c.lo: 0x5 d: 0x6 d.hi: 0x6 d.lo: 0x7 x: 0x8 x.hi: 0x8 x.lo: 0x9 y: 0xa y.hi: 0xa y.lo: 0xb i: 0xc i.hi: 0xc i.lo: 0xd j: 0xe j.hi: 0xe j.lo: 0xf p: 0x10 p.hi: 0x10 p.lo: 0x11 q: 0x12 q.hi: 0x12 q.lo: 0x13 sp: 0x14 sp.hi: 0x14 sp.lo: 0x15 pc: 0x16 pc.hi: 0x16 pc.lo: 0x17 Finally: <regi> is one of x, y, i, j <rega> is one of p, q, sp, pc

Operands are now simply 8 bits and denote memory addresses 0x00 to 0xff. I have a slightly more complex design in mind but let's get some syntax defined first. This also details how to address lower and higher parts of registers:

16.10.2023 13:26 πŸ‘ 0 πŸ” 0 πŸ’¬ 1 πŸ“Œ 0