211 lines
5.4 KiB
C
211 lines
5.4 KiB
C
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
|
|
// Q16.16 fixed-point macros
|
|
#define Q16_16_SCALE 16
|
|
#define Q16_16_FRACTION_MASK 0x0000FFFF
|
|
#define TO_Q16_16(x) \
|
|
((int32_t)((x) << Q16_16_SCALE)) // Convert integer to Q16.16
|
|
#define FROM_Q16_16(x) \
|
|
((int32_t)((x) >> Q16_16_SCALE)) // Convert Q16.16 to integer
|
|
|
|
#define MEMORY_SIZE 65536
|
|
int32_t memory[MEMORY_SIZE]; // Changed to signed for fixed-point support
|
|
|
|
typedef enum { OP_ADD, OP_MOV, OP_JMP, OP_HALT } Opcode;
|
|
|
|
|
|
// Convert string to Q16.16 fixed-point
|
|
int32_t string_to_q16_16(const char *str) {
|
|
int32_t result = 0;
|
|
int sign = 1;
|
|
|
|
// Handle sign
|
|
if (*str == '-') {
|
|
sign = -1;
|
|
str++;
|
|
}
|
|
|
|
// Parse integer part
|
|
int32_t integer_part = 0;
|
|
while (*str != '.' && *str != '\0') {
|
|
if (*str < '0' || *str > '9') {
|
|
return 0; // Invalid character
|
|
}
|
|
integer_part = integer_part * 10 + (*str - '0');
|
|
str++;
|
|
}
|
|
|
|
// Clamp integer part to 16-bit range
|
|
if (integer_part > 32767) {
|
|
integer_part = 32767; // Overflow clamp
|
|
}
|
|
|
|
// Parse fractional part
|
|
uint32_t frac_digits = 0;
|
|
int frac_count = 0;
|
|
|
|
if (*str == '.') {
|
|
str++; // Skip decimal point
|
|
while (*str != '\0' && frac_count < 6) {
|
|
if (*str < '0' || *str > '9') {
|
|
break;
|
|
}
|
|
frac_digits = frac_digits * 10 + (*str - '0');
|
|
frac_count++;
|
|
str++;
|
|
}
|
|
}
|
|
|
|
// Scale fractional part to Q16.16
|
|
// frac_q16 = (frac_digits * 65536 + 500000) / 1000000
|
|
// This avoids floating-point by using integer arithmetic
|
|
uint32_t scaled_frac = 0;
|
|
if (frac_count > 0) {
|
|
// Pad with zeros if less than 6 digits
|
|
while (frac_count < 6) {
|
|
frac_digits *= 10;
|
|
frac_count++;
|
|
}
|
|
// Compute scaled fractional part with rounding
|
|
scaled_frac = (frac_digits * 65536 + 500000) / 1000000;
|
|
if (scaled_frac > 0xFFFF) {
|
|
scaled_frac = 0xFFFF; // Clamp to 16-bit range
|
|
}
|
|
}
|
|
|
|
// Combine integer and fractional parts
|
|
result = (integer_part << Q16_16_SCALE) | (scaled_frac & Q16_16_FRACTION_MASK);
|
|
return result * sign;
|
|
}
|
|
|
|
// Convert Q16.16 to string using integer-only arithmetic
|
|
int q16_16_to_string(int32_t value, char *buffer, size_t size) {
|
|
if (size < 13) { // Minimum buffer size for "-32768.000000\0"
|
|
if (size > 0)
|
|
buffer[0] = '\0';
|
|
return -1; // Buffer too small
|
|
}
|
|
|
|
char *buf = buffer;
|
|
size_t remaining = size;
|
|
|
|
// Handle sign
|
|
if (value < 0) {
|
|
*buf++ = '-';
|
|
value = -value;
|
|
remaining--;
|
|
}
|
|
|
|
// Extract integer and fractional parts
|
|
int32_t integer_part = value >> Q16_16_SCALE;
|
|
uint32_t frac = value & Q16_16_FRACTION_MASK;
|
|
|
|
// Convert integer part to string
|
|
char int_buf[11]; // Max 10 digits for 32-bit int
|
|
char *int_end = int_buf + sizeof(int_buf);
|
|
char *int_ptr = int_end;
|
|
|
|
// Special case for zero
|
|
if (integer_part == 0) {
|
|
*--int_ptr = '0';
|
|
} else {
|
|
while (integer_part > 0 && int_ptr > int_buf) {
|
|
*--int_ptr = '0' + (integer_part % 10);
|
|
integer_part /= 10;
|
|
}
|
|
}
|
|
|
|
// Copy integer part to output buffer
|
|
while (int_ptr < int_end && remaining > 1) {
|
|
*buf++ = *int_ptr++;
|
|
remaining--;
|
|
}
|
|
|
|
// Add decimal point
|
|
if (remaining > 7) { // Need space for .000000
|
|
*buf++ = '.';
|
|
remaining--;
|
|
} else {
|
|
if (remaining > 0)
|
|
*buf = '\0';
|
|
return size - remaining; // Truncate if insufficient space
|
|
}
|
|
|
|
// Convert fractional part to 6 digits
|
|
for (int i = 0; i < 6 && remaining > 1; i++) {
|
|
frac *= 10;
|
|
uint32_t digit = frac >> 16;
|
|
frac &= 0xFFFF;
|
|
*buf++ = '0' + digit;
|
|
remaining--;
|
|
}
|
|
|
|
// Null-terminate
|
|
*buf = '\0';
|
|
return buf - buffer; // Return length written
|
|
}
|
|
|
|
void run_vm() {
|
|
uint32_t pc = 0;
|
|
while (1) {
|
|
Opcode opcode = memory[pc];
|
|
uint32_t src1_addr = memory[pc + 1];
|
|
uint32_t src2_addr = memory[pc + 2];
|
|
uint32_t dest_addr = memory[pc + 3];
|
|
pc += 4;
|
|
|
|
if (src1_addr >= MEMORY_SIZE || src2_addr >= MEMORY_SIZE ||
|
|
dest_addr >= MEMORY_SIZE) {
|
|
printf("Invalid memory address!\n");
|
|
exit(1);
|
|
}
|
|
|
|
switch (opcode) {
|
|
case OP_ADD:
|
|
// Q16.16 addition requires no scaling adjustment
|
|
memory[dest_addr] = memory[src1_addr] + memory[src2_addr];
|
|
break;
|
|
case OP_MOV:
|
|
// Direct copy preserves fixed-point representation
|
|
memory[dest_addr] = memory[src1_addr];
|
|
break;
|
|
case OP_JMP:
|
|
pc = src1_addr;
|
|
break;
|
|
case OP_HALT:
|
|
return;
|
|
default:
|
|
printf("Unknown opcode: %d\n", opcode);
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
// Initialize memory with Q16.16 values
|
|
memory[0] = OP_ADD;
|
|
memory[1] = 100;
|
|
memory[2] = 101;
|
|
memory[3] = 102;
|
|
const char *input = "-5.0";
|
|
memory[100] = string_to_q16_16(input); // Convert "-5.0" to Q16.16
|
|
const char *input2 = "10.0";
|
|
memory[101] = string_to_q16_16(input2); // Convert "10.0" to Q16.16
|
|
memory[4] = OP_HALT;
|
|
|
|
run_vm();
|
|
|
|
char buffer[13]; // Sufficient for Q16.16 format
|
|
|
|
// Convert result back to integer
|
|
q16_16_to_string(memory[102], buffer, sizeof(buffer));
|
|
printf("Result at address 102: %s\n", buffer); // Output: 5.0
|
|
return 0;
|
|
}
|