Spaces:
Runtime error
Runtime error
<title>v86: sectorc</title> | |
<script src="../build/libv86.js"></script> | |
<script> | |
; | |
window.onload = function() | |
{ | |
const libc = `int tmp1; | |
int tmp2; | |
void shutdown() | |
{ | |
/* Shutdown via APM: coded in asm machine code directly */ | |
// Check for APM | |
// | mov ah,0x53; mov al,0x00; xor bx,bx; int 0x15; jc error | |
asm 180; asm 83; asm 176; asm 0; asm 49; asm 219; | |
asm 205; asm 21; asm 114; asm 55; | |
// Disconnect from any APM interface | |
// | mov ah,0x53; mov al,0x04; xor bx,bx; int 0x15 | |
// | jc maybe_error; jmp no_error | |
asm 180; asm 83; asm 176; asm 4; asm 49; asm 219; | |
asm 205; asm 21; asm 114; asm 2; asm 235; asm 5; | |
// Label: maybe_error | |
// | cmp ah,0x03; jne error | |
asm 128; asm 252; asm 3; asm 117; asm 38; | |
// Label: no_error | |
// Connect to APM interface | |
// | mov ah,0x53; mov al,0x01; xor bx,bx; int 0x15; jc error | |
asm 180; asm 83; asm 176; asm 1; asm 49; asm 219; | |
asm 205; asm 21; asm 114; asm 28; | |
// Enable power management for all devices | |
// | mov ah,0x53; mov al,0x08; mov bx,0x0001; mov cx,0x0001 | |
// | int 0x15; jc error | |
asm 180; asm 83; asm 176; asm 8; | |
asm 187; asm 1; asm 0; asm 185; asm 1; asm 0; | |
asm 205; asm 21; asm 114; asm 14; | |
// Set the power state for all devices | |
// | mov ah,0x53; mov al,0x7; mov bx,0x0001; mov cx,0x0003 | |
// | int 0x15; jc error | |
asm 180; asm 83; asm 176; asm 7; | |
asm 187; asm 1; asm 0; asm 185; asm 3; asm 0; | |
asm 205; asm 21; asm 114; asm 0; | |
// Label: error | |
// | hlt; jmp error | |
asm 244; asm 235; asm 253; | |
} | |
int store_far_seg; | |
int store_far_off; | |
int store_far_val; | |
void store_far() | |
{ | |
// mov es, store_far_seg | |
store_far_seg = store_far_seg; | |
asm 142; asm 192; | |
// mov si, store_far_off | |
store_far_off = store_far_off; | |
asm 137; asm 198; | |
// mov es:[si], store_far_val | |
store_far_val = store_far_val; | |
asm 38; asm 137; asm 4; | |
} | |
int div10_unsigned_n; | |
int div10_unsigned_q; | |
int div10_unsigned_r; | |
void div10_unsigned() | |
{ | |
/* Taken from "Hacker's Delight", modified to "fit your screen" */ | |
tmp1 = ( div10_unsigned_n >> 1 ) & 32767; // unsigned | |
tmp2 = ( div10_unsigned_n >> 2 ) & 16383; // unsigned | |
div10_unsigned_q = tmp1 + tmp2; | |
tmp1 = ( div10_unsigned_q >> 4 ) & 4095; // unsigned | |
div10_unsigned_q = div10_unsigned_q + tmp1; | |
tmp1 = ( div10_unsigned_q >> 8 ) & 255; // unsigned | |
div10_unsigned_q = div10_unsigned_q + tmp1; | |
div10_unsigned_q = ( div10_unsigned_q >> 3 ) & 8191; // unsigned | |
div10_unsigned_r = div10_unsigned_n | |
- ( ( div10_unsigned_q << 3 ) + ( div10_unsigned_q << 1 ) ); | |
if( div10_unsigned_r > 9 ){ | |
div10_unsigned_q = div10_unsigned_q + 1; | |
div10_unsigned_r = div10_unsigned_r - 10; | |
} | |
} | |
int print_ch; | |
void print_char() | |
{ | |
/* Implement print char via serial port bios function accessed via int 0x14 */ | |
print_ch = print_ch; // mov ax,[&print_ch] | |
asm 180; asm 1; // mov ah,1 | |
asm 186; asm 0; asm 0 ; // mov dx,0 | |
asm 205; asm 20; // int 0x14 | |
} | |
// uses 'print_ch' | |
void print_newline() | |
{ | |
print_ch = 10; | |
print_char(); | |
} | |
int print_num; // input | |
int print_u16_bufptr; | |
int print_u16_cur; | |
void print_u16() | |
{ | |
print_u16_bufptr = 30000; // buffer for ascii digits | |
if( print_num == 0 ){ | |
print_ch = 48; | |
print_char(); | |
} | |
print_u16_cur = print_num; | |
while( print_u16_cur != 0 ){ | |
div10_unsigned_n = print_u16_cur; | |
div10_unsigned(); | |
*(int*) print_u16_bufptr = div10_unsigned_r; | |
print_u16_bufptr = print_u16_bufptr + 1; | |
print_u16_cur = div10_unsigned_q; | |
} | |
while( print_u16_bufptr != 30000 ){ // emit them in reverse over | |
print_u16_bufptr = print_u16_bufptr - 1; | |
print_ch = ( *(int*) print_u16_bufptr & 255 ) + 48; | |
print_char(); | |
} | |
} | |
// uses 'print_num' and 'print_ch' | |
void print_i16() | |
{ | |
if( print_num < 0 ){ | |
print_ch = 45; print_char(); // '-' | |
print_num = 0 - print_num; | |
} | |
print_u16(); | |
} | |
void vga_init() | |
{ | |
// mov ah,0; mov al,0x13; int 0x10 | |
asm 180; asm 0; asm 176; asm 19; asm 205; asm 16; | |
} | |
void vga_clear() | |
{ | |
// push di; xor di,di; mov bx,0xa000; mov es,bx; | |
// mov cx,0x7d00; xor ax,ax; rep stos; pop di | |
asm 87 ; asm 49 ; asm 255; asm 187; asm 0; asm 160; | |
asm 142; asm 195; asm 185; asm 0; asm 125; asm 49; | |
asm 192; asm 243; asm 171; asm 95; | |
} | |
int pixel_x; | |
int pixel_y; | |
void vga_set_pixel() | |
{ | |
// need to multiply pixel_y by 320 = 256 + 64 | |
// use 'tmp1' for pixel index | |
tmp1 = ( ( pixel_y << 8 ) + ( pixel_y << 6 ) ) + pixel_x; | |
// store to 0xa000:pixel_idx | |
// mov bx,0xa000; mov es,bx; mov bx,ax; mov BYTE PTR es:[bx],0xf | |
tmp1 = tmp1; | |
asm 187; asm 0; asm 160; asm 142; asm 195; | |
asm 137; asm 195; asm 38; asm 198; asm 7; asm 15; | |
} | |
int port_num; | |
int port_val; | |
void port_inb() | |
{ | |
dx = port_num; | |
// mov dx,WORD PTR [0x464]; in al,dx | |
asm 139; asm 22; asm 160; asm 4; asm 236; | |
// mov WORD PTR [0x464],ax | |
asm 137; asm 6; asm 100; asm 4; | |
port_val = ax; | |
} | |
void port_inw() | |
{ | |
// mov dx,WORD PTR [0x464]; in ax,dx | |
dx = port_num; | |
asm 139; asm 22; asm 160; asm 4; asm 237; | |
// mov WORD PTR [0x464],ax | |
asm 137; asm 6; asm 100; asm 4; | |
port_val = ax; | |
} | |
void port_outb() | |
{ | |
dx = port_num; | |
ax = port_val; | |
// mov dx,WORD PTR [0x464] | |
asm 139; asm 22; asm 160; asm 4; | |
// mov ax,WORD PTR [0x464] | |
asm 139; asm 6; asm 100; asm 4; | |
// outb dx,al | |
asm 238; | |
} | |
void port_outw() | |
{ | |
dx = port_num; | |
ax = port_val; | |
// mov dx,WORD PTR [0x464] | |
asm 139; asm 22; asm 160; asm 4; | |
// mov ax,WORD PTR [0x464] | |
asm 139; asm 6; asm 100; asm 4; | |
// outb dx,al | |
asm 239; | |
} | |
void dump_code_segment_and_shutdown() | |
{ | |
/* NOTE: This code is in a different segment from data, and our compiled pointer accesses | |
do not leave the data segment, so we need a little machine code to grab data from the | |
code segment and stash it in a variable for C */ | |
i = 0; | |
while( i < 8192 ){ /* Just assuming 8K is enough.. might not be true */ | |
// (put "i" in ax); mov si,ax; mov ax,cs:[si]; mov [&a],ax | |
i = i; asm 137; asm 198; asm 46; asm 139; asm 4; asm 137; asm 133; asm 98; asm 0; | |
print_ch = a; | |
print_char(); | |
i = i + 1; | |
} | |
shutdown(); | |
} | |
`; | |
const start = `void _start() | |
{ | |
main(); | |
shutdown(); | |
} | |
`; | |
const hello = `int buf; | |
int ptr; | |
int len; | |
void vga_write() | |
{ | |
/* Text vga is located at b800:0000 */ | |
store_far_seg = 47104; // segment: 0xb800 | |
store_far_off = idx << 1; | |
store_far_val = ( 15 << 8 ) | ( ch & 255 ); // white fg and black bg | |
store_far(); | |
} | |
int x_off; | |
int y_off; | |
void vga_write_ch() | |
{ | |
if( ch != 10 ){ | |
idx = y_off + x_off; | |
vga_write(); | |
x_off = x_off + 1; | |
} | |
if( ( ch == 10 ) | ( x_off == 80 ) ){ | |
y_off = y_off + 80; | |
x_off = 0; | |
} | |
} | |
int idx; | |
void vga_clear() | |
{ | |
idx = 0; | |
while( idx < 2000 ){ // 80x25 | |
ch = 32; // char: ' ' | |
vga_write(); | |
idx = idx + 1; | |
} | |
pos = 0; | |
} | |
void main() | |
{ | |
// dump_code_segment_and_shutdown(); | |
vga_clear(); | |
ch = 72; vga_write_ch(); | |
ch = 101; vga_write_ch(); | |
ch = 108; vga_write_ch(); | |
ch = 108; vga_write_ch(); | |
ch = 111; vga_write_ch(); | |
ch = 10; vga_write_ch(); | |
ch = 32; vga_write_ch(); | |
ch = 102; vga_write_ch(); | |
ch = 114; vga_write_ch(); | |
ch = 111; vga_write_ch(); | |
ch = 109; vga_write_ch(); | |
ch = 10; vga_write_ch(); | |
ch = 32; vga_write_ch(); | |
ch = 32; vga_write_ch(); | |
ch = 83; vga_write_ch(); | |
ch = 101; vga_write_ch(); | |
ch = 99; vga_write_ch(); | |
ch = 116; vga_write_ch(); | |
ch = 111; vga_write_ch(); | |
ch = 114; vga_write_ch(); | |
ch = 67; vga_write_ch(); | |
ch = 10; vga_write_ch(); | |
ch = 32; vga_write_ch(); | |
ch = 32; vga_write_ch(); | |
ch = 32; vga_write_ch(); | |
i = 0; | |
while( i < 10 ){ | |
ch = 33; vga_write_ch(); | |
i = i + 1; | |
} | |
while( 1 ){ } | |
} | |
`; | |
const sinwave = `/* A Sine-wave Animation | |
Math time: | |
--------------------------- | |
Along the range [0, pi] we can approximate sin(x) very crudely with a 2nd order quadratic | |
That is: y = a * x^2 + b * x + c | |
Three unknowns need three constraints, so picking the easy ones: | |
x = 0, y = 0 | |
x = pi/2, y = 1 | |
x = pi, y = 0 | |
Solving the linear system: | |
| 0 0 1 | | a | | 0 | | |
| pi^2/4 pi/2 1 | * | b | = | 1 | | |
| pi^2 pi 1 | | c | | 0 | | |
We get: | |
a = -4 / pi^2 | |
b = 4 / pi | |
c = 0 | |
And: | |
y = 4x(pi - x)/(pi^2) | |
Engineering time: | |
--------------------------- | |
We are working with a 320x200 vga. We also don't have floating-point math. So, the | |
goal here is to do all the math in integer screen coordinates and accept some pixel | |
approximation error. | |
First, we want to center the wave in the middle, y = 100 | |
We'll let y vary +-50 pixels to remain on the screen, so [50, 150] | |
We want to show an entire cycle (2pi) on the x-axis, so *50 gives us [0, ~314] | |
This implies that the "x-origin" is at x = 157 | |
Substituting in everything, we get: | |
y ~= 100 + x*(157 - x)/125 | |
The division by 125 is problematic as we don't have division. But luckily 128 is close enough. | |
Thus, we get: | |
y ~= 100 + (x*(157 - x)) >> 7 | |
The rest is just adjusting for the [0, pi] range reduction by negating the approximation | |
along [pi, 2pi] | |
NOTE: the screen coordinate system is upside-down and I don't bother to correct for that. | |
it simply means that the animation starts at a +pi phase offset | |
*/ | |
int y; | |
int x; | |
int x_0; | |
void sin_positive_approx() | |
{ | |
y = ( x_0 * ( 157 - x_0 ) ) >> 7; | |
} | |
void sin() | |
{ | |
x_0 = x; | |
while( x_0 > 314 ){ | |
x_0 = x_0 - 314; | |
} | |
if( x_0 <= 157 ){ | |
sin_positive_approx(); | |
} | |
if( x_0 > 157 ){ | |
x_0 = x_0 - 157; | |
sin_positive_approx(); | |
y = 0 - y; | |
} | |
y = 100 + y; | |
} | |
int offset; | |
int x_end; | |
void draw_sine_wave() | |
{ | |
x = offset; | |
x_end = x + 314; | |
while( x <= x_end ){ | |
sin(); | |
pixel_x = x - offset; | |
pixel_y = y; | |
vga_set_pixel(); | |
x = x + 1; | |
} | |
} | |
int v_1; | |
int v_2; | |
void delay() | |
{ | |
v_1 = 0; | |
while( v_1 < 50 ){ | |
v_2 = 0; | |
while( v_2 < 10000 ){ | |
v_2 = v_2 + 1; | |
} | |
v_1 = v_1 + 1; | |
} | |
} | |
void main() | |
{ | |
vga_init(); | |
offset = 0; | |
while( 1 ){ | |
vga_clear(); | |
draw_sine_wave(); | |
delay(); | |
offset = offset + 1; | |
if( offset >= 314 ){ // mod the value to avoid 2^16 integer overflow | |
offset = offset - 314; | |
} | |
} | |
} | |
`; | |
const twinkle = `/* References: | |
http://muruganad.com/8086/8086-assembly-language-program-to-play-sound-using-pc-speaker.html | |
https://en.wikipedia.org/wiki/Twinkle,_Twinkle,_Little_Star | |
*/ | |
void delay_1() | |
{ | |
v_1 = 0; | |
while( v_1 < 4000 ){ | |
v_2 = 0; | |
while( v_2 < 10000 ){ | |
v_2 = v_2 + 1; | |
} | |
v_1 = v_1 + 1; | |
} | |
} | |
void delay_2() | |
{ | |
v_1 = 0; | |
while( v_1 < 300 ){ | |
v_2 = 0; | |
while( v_2 < 10000 ){ | |
v_2 = v_2 + 1; | |
} | |
v_1 = v_1 + 1; | |
} | |
} | |
void audio_init() | |
{ | |
// Configure PIC2 mode | |
port_num = 67; | |
port_val = 182; | |
port_outb(); | |
} | |
void audio_enable() | |
{ | |
// Set bits 0 and 1 to enable | |
port_num = 97; | |
port_inb(); | |
port_val = port_val | 3; | |
port_outb(); | |
} | |
void audio_disable() | |
{ | |
// Clear bits 0 and 1 to enable | |
port_num = 97; | |
port_inb(); | |
port_val = port_val & 65532; | |
port_outb(); | |
} | |
int audio_freq; | |
void audio_freq_set() | |
{ | |
// Set frequency | |
port_num = 66; | |
port_val = audio_freq & 255; | |
port_outb(); | |
port_val = ( audio_freq >> 8 ) & 255; | |
port_outb(); | |
} | |
int note; | |
void play_quarter_note() | |
{ | |
audio_freq = note; | |
audio_freq_set(); | |
audio_enable(); | |
delay_1(); | |
audio_disable(); | |
delay_2(); | |
} | |
void play_half_note() | |
{ | |
audio_freq = note; | |
audio_freq_set(); | |
audio_enable(); | |
delay_1(); | |
delay_1(); | |
audio_disable(); | |
delay_2(); | |
} | |
void play_section_1() | |
{ | |
note = C; play_quarter_note(); | |
note = C; play_quarter_note(); | |
note = G; play_quarter_note(); | |
note = G; play_quarter_note(); | |
note = A; play_quarter_note(); | |
note = A; play_quarter_note(); | |
note = G; play_half_note(); | |
note = F; play_quarter_note(); | |
note = F; play_quarter_note(); | |
note = E; play_quarter_note(); | |
note = E; play_quarter_note(); | |
note = D; play_quarter_note(); | |
note = D; play_quarter_note(); | |
note = C; play_half_note(); | |
} | |
void play_section_2() | |
{ | |
note = G; play_quarter_note(); | |
note = G; play_quarter_note(); | |
note = F; play_quarter_note(); | |
note = F; play_quarter_note(); | |
note = E; play_quarter_note(); | |
note = E; play_quarter_note(); | |
note = D; play_half_note(); | |
note = G; play_quarter_note(); | |
note = G; play_quarter_note(); | |
note = F; play_quarter_note(); | |
note = F; play_quarter_note(); | |
note = E; play_quarter_note(); | |
note = E; play_quarter_note(); | |
note = D; play_half_note(); | |
} | |
void main() | |
{ | |
audio_init(); | |
audio_enable(); | |
C = 4560; | |
D = 4063; | |
E = 3619; | |
F = 3416; | |
G = 3043; | |
A = 2711; | |
play_section_1(); | |
play_section_2(); | |
play_section_1(); | |
audio_disable(); | |
} | |
`; | |
document.getElementById("source").onkeydown = function(e) | |
{ | |
if(e.which == 13 && e.ctrlKey) | |
{ | |
document.getElementById("run").onclick(); | |
} | |
}; | |
document.getElementById("source").textContent = sinwave; | |
document.getElementById("source").onkeydown = function(e) | |
{ | |
if(e.which == 13 && e.ctrlKey) | |
{ | |
document.getElementById("run").onclick(); | |
} | |
}; | |
document.getElementById("examples").onchange = function() | |
{ | |
document.getElementById("source").textContent = { sinwave, twinkle, hello }[this.value]; | |
}; | |
let emulator; | |
document.getElementById("run").onclick = run; | |
function run() | |
{ | |
emulator && emulator.destroy(); | |
emulator = window.emulator = new V86({ | |
wasm_path: "../build/v86.wasm", | |
memory_size: 32 * 1024 * 1024, | |
vga_memory_size: 2 * 1024 * 1024, | |
screen_container: document.getElementById("screen_container"), | |
bios: { url: "../bios/seabios.bin" }, | |
vga_bios: { url: "../bios/vgabios.bin" }, | |
fda: { url: "../images/sectorc.bin" }, | |
autostart: true, | |
}); | |
emulator.add_listener("emulator-ready", () => { | |
const source = libc + document.getElementById("source").value + start; | |
emulator.serial0_send(source); | |
}); | |
document.getElementById("run").onclick = stop; | |
document.getElementById("run").textContent = "stop"; | |
}; | |
function stop() | |
{ | |
emulator && emulator.destroy(); | |
document.getElementById("run").onclick = run; | |
document.getElementById("run").textContent = "run (ctrl-enter)"; | |
} | |
} | |
</script> | |
<br> | |
<textarea id=source rows=20 cols=80> | |
</textarea> | |
<br> | |
<select id=examples> | |
<option value=sinwave>Sine wave</option> | |
<option value=hello>Hello</option> | |
<option value=twinkle>Twinkle (audio)</option> | |
</select> | |
<button id=run>run (ctrl-enter)</button> | |
<br> | |
<hr> | |
<div id="screen_container"> | |
<div style="white-space: pre; font: 14px monospace; line-height: 14px"></div> | |
<canvas style="display: none"></canvas> | |
</div> | |
<hr> | |
<a href="https://github.com/xorvoid/sectorc">sectorc</a> on <a href="/v86/">v86</a> | |