-- -----------------------------------------------------------------------------
-- 
-- riscv.sql
-- Jamie Wood 2016
-- 
-- An implementation of an emulator for most of the RV32I base instruction set.
-- Compile any code with the riscv32-unknown-elf- toolchain (only base RV32I
--  instruction set allowed), load the hexdump-ed .text section into the
--  `raw_prog` table, and call decode_program().
--  Load the hexdump-ed .data section into the `raw_data` table.
-- 
-- Call reset_emulator() to reset the registers and memory for a clean run.
-- Call execute_program() to run the program.
-- 
-- Input is performed only via memory reads, which must be setup ahead of time.
-- Output is available by writing to the address 0x87000000. Any 32-bit words
--  written there will be stored as strings in the `output` table.
-- 
-- Performance is not a feature.
-- 
-- -----------------------------------------------------------------------------

-- Raw machine code storage
drop table if exists raw_prog;
create table raw_prog(prog_addr int primary key auto_increment,
    str_instr char(8));

-- Raw data memory storage
drop table if exists raw_data;
create table raw_data(data_addr int primary key auto_increment,
    str_data char(8));

-- Disassembled instruction storage
drop table if exists program;
create table program(prog_addr int unsigned primary key, operation int,
    reg_d int, reg_s1 int, reg_s2 int, immediate long);

-- Registers and RAM tables are stored as MEMORY to increase performance
drop table if exists registers;
create table registers(reg_id int unsigned primary key, reg_val int unsigned)
    engine = memory;

drop table if exists ram;
create table ram(mem_addr int unsigned primary key, mem_val int unsigned)
    engine = memory;

-- Output table which stores values written to the magic 0x87000000 address
drop table if exists output;
create table output(output_id int auto_increment primary key, msg varchar(8));

-- Opeartor constants for readability
set @I_LB = 1;
set @I_LH = 2;
set @I_LW = 3;
set @I_LBU = 4;
set @I_LHU = 5;
set @I_FENCE_I = 6;
set @I_FENCE = 7;
set @I_ADDI = 8;
set @I_SLLI = 9;
set @I_SLTI = 10;
set @I_SLTIU = 11;
set @I_XORI = 12;
set @I_SRLI = 13;
set @I_SRAI = 14;
set @I_ORI = 15;
set @I_ANDI = 16;
set @I_AUIPC = 17;
set @I_SB = 18;
set @I_SH = 19;
set @I_SW = 20;
set @I_ADD = 21;
set @I_SUB = 22;
set @I_SLL = 23;
set @I_SLT = 24;
set @I_SLTU = 25;
set @I_XOR = 26;
set @I_SRL = 27;
set @I_SRA = 28;
set @I_OR = 29;
set @I_AND = 30;
set @I_LUI = 31;
set @I_BEQ = 32;
set @I_BNE = 33;
set @I_BLT = 34;
set @I_BGE = 35;
set @I_BLTU = 36;
set @I_BGEU = 37;
set @I_JALR = 38;
set @I_JAL = 39;
set @I_ERROR = 255;

-- Delete functions and procedures if they're already defined
drop function if exists sign_extend;
drop function if exists signed_to_unsigned;
drop procedure if exists decode_R;
drop procedure if exists decode_I;
drop procedure if exists decode_S;
drop procedure if exists decode_SB;
drop procedure if exists decode_U;
drop procedure if exists decode_UJ;
drop procedure if exists instr_decode;
drop procedure if exists write_reg;
drop function if exists read_reg;
drop function if exists mem_read_byte;
drop function if exists mem_read_halfword;
drop function if exists mem_read_word;
drop procedure if exists mem_write_byte;
drop procedure if exists mem_write_halfword;
drop procedure if exists mem_write_word;
drop procedure if exists instr_execute;
drop procedure if exists decode_program;
drop procedure if exists load_mem;
drop function if exists execute_program;
drop procedure if exists reset_emulator;

delimiter $$

-- -------------------------------------------------------- --
-- Sign extend a number, stored as a certain number of bits --
-- -------------------------------------------------------- --
create function sign_extend(val long, bits int) returns int signed
begin
    set @sign_bit = 1 << (bits - 1);
    return cast((val & (@sign_bit - 1)) as signed) -
        cast((val & @sign_bit) as signed);
end $$

-- ------------------------------------------------ --
-- Convert a signed value back to an unsigned value --
-- ------------------------------------------------ --
create function signed_to_unsigned(val long, bits int) returns long
begin
    return (1 << bits) + val;
end $$

-- -------------------------------- --
-- Pull apart an R-type instruction --
-- -------------------------------- --
create procedure decode_R(in instr int, out funct7 int, out rs2 int,
    out rs1 int, out funct3 int, out rd int) deterministic
begin
    set funct7 = (instr >> 25) & 0x7f;
    set rs2 = (instr >> 20) & 0x1f;
    set rs1 = (instr >> 15) & 0x1f;
    set funct3 = (instr >> 12) & 0x07;
    set rd = (instr >> 7) & 0x1f;
end $$

-- -------------------------------- --
-- Pull apart an I-type instruction --
-- -------------------------------- --
create procedure decode_I(in instr int, out imm int, out rs1 int,
    out funct3 int, out rd int) deterministic
begin
    set imm = (instr >> 20) & 0xfff;
    set rs1 = (instr >> 15) & 0x1f;
    set funct3 = (instr >> 12) & 0x07;
    set rd = (instr >> 7) & 0x1f;
end $$

-- -------------------------------- --
-- Pull apart an S-type instruction --
-- -------------------------------- --
create procedure decode_S(in instr int, out imm int, out rs2 int, out rs1 int,
    out funct3 int) deterministic
begin
    set imm = (((instr >> 25) &  0x7f) << 5) | ((instr >> 7) & 0x1f);
    set rs2 = (instr >> 20) & 0x1f;
    set rs1 = (instr >> 15) & 0x1f;
    set funct3 = (instr >> 12) & 0x07;
end $$

-- --------------------------------- --
-- Pull apart an SB-type instruction --
-- --------------------------------- --
create procedure decode_SB(in instr int, out imm int, out rs2 int, out rs1 int,
    out funct3 int) deterministic
begin
    set imm = ((instr >> 31) & 0x01) << 12;
    set imm = imm | (((instr >> 7) & 0x01) << 11);
    set imm = imm | (((instr >> 25) & 0x3f) << 5);
    set imm = imm | (((instr >> 8) & 0x0f) << 1);
    set rs2 = (instr >> 20) & 0x1f;
    set rs1 = (instr >> 15) & 0x1f;
    set funct3 = (instr >> 12) & 0x07;
end $$

-- ------------------------------- --
-- Pull apart a U-type instruction --
-- ------------------------------- --
create procedure decode_U(in instr int, out imm long, out rd int) deterministic
begin
    set imm = instr & 0xfffff000;
    set rd = (instr >> 7) & 0x1f;
end $$

-- -------------------------------- --
-- Pull apart a UJ-type instruction --
-- -------------------------------- --
create procedure decode_UJ(in instr int, out imm int, out rd int) deterministic
begin
    set imm = ((instr >> 31) & 0x01) << 20;
    set imm = imm | (((instr >> 12) & 0xff) << 12);
    set imm = imm | (((instr >> 20) & 0x01) << 11);
    set imm = imm | (((instr >> 21) & 0x3ff) << 1);
    set rd = (instr >> 7) & 0x1f;
end $$

-- ---------------------------------------------------------------- --
-- Dissassemble an RV32I instruction into an operation and operands --
-- ---------------------------------------------------------------- --
create procedure instr_decode(in instr_addr int, in instr int unsigned)
    not deterministic
begin
    set @opcode = (instr >> 2) & 0x1f;
    if @opcode = 0 then -- LB, LH, LW, LBU, LHU
        call decode_I(instr, @imm, @rs1, @funct3, @rd);
        if @funct3 = 0 then
            insert into program values (instr_addr, @I_LB, @rd, @rs1, null,
                sign_extend(@imm, 12));
        elseif @funct3 = 1 then
            insert into program values (instr_addr, @I_LH, @rd, @rs1, null,
                sign_extend(@imm, 12));
        elseif @funct3 = 2 then
            insert into program values (instr_addr, @I_LW, @rd, @rs1, null,
                sign_extend(@imm, 12));
        elseif @funct3 = 4 then
            insert into program values (instr_addr, @I_LBU, @rd, @rs1, null,
                sign_extend(@imm, 12));
        elseif @funct3 = 5 then
            insert into program values (instr_addr, @I_LHU, @rd, @rs1, null,
                sign_extend(@imm, 12));
        else
            insert into program values (instr_addr, @I_ERROR, null, null, null,
                null);
        end if;
    elseif @opcode = 3 then -- FENCE, FENCE.I
        set @pred = (instr >> 24) & 0x0f;
        set @succ = (instr >> 20) & 0x0f;
        if @pred = @succ = 0 then
            insert into program values(instr_addr, @I_FENCE_I, null, null, null,
                null);
        else
            insert into program values(instr_addr, @I_FENCE, @pred, @succ, null,
                null);
        end if;
    elseif @opcode = 4 then -- ADDI, SLTI, SLTIU, XORI, ORI, ANDI, SLLI, SRLI, SRAI
        call decode_I(instr, @imm, @rs1, @funct3, @rd);
        if @funct3 = 0 then
            insert into program values(instr_addr, @I_ADDI, @rd, @rs1, null,
                sign_extend(@imm, 12));
        elseif @funct3 = 1 then
            set @shamt = sign_extend(@imm & 0x1f, 5);
            insert into program values(instr_addr, @I_SLLI, @rd, @rs1, null,
                @shamt);
        elseif @funct3 = 2 then
            insert into program values(instr_addr, @I_SLTI, @rd, @rs1, null,
                sign_extend(@imm, 12));
        elseif @funct3 = 3 then
            insert into program values(instr_addr, @I_SLTIU, @rd, @rs1, null,
                @imm);
        elseif @funct3 = 4 then
            insert into program values(instr_addr, @I_XORI, @rd, @rs1, null,
                sign_extend(@imm, 12));
        elseif @funct3 = 5 then
            set @funct7 = (@imm >>5) & 0x7f;
            if @funct7 = 0 then
                set @shamt = sign_extend(@imm & 0x1f, 5);
                insert into program values(instr_addr, @I_SRLI, @rd, @rs1, null,
                    @shamt);
            elseif @funct7 = 0x20 then
                set @shamt = @imm & 0x1f;
                insert into program values(instr_addr, @I_SRAI, @rd, @rs1, null,
                    @shamt);
            else
                insert into program values(instr_addr, @I_ERROR, null, null,
                    null, null);
            end if;
        elseif @funct3 = 6 then
            insert into program values(instr_addr, @I_ORI, @rd, @rs1, null,
                sign_extend(@imm, 12));
        elseif @funct3 = 7 then
            insert into program values(instr_addr, @I_ANDI, @rd, @rs1, null,
                sign_extend(@imm, 12));
        else
            insert into program values(instr_addr, @I_ERROR, null, null, null,
                null);
        end if;
    elseif @opcode = 5 then -- AUIPC
        call decode_U(instr, @imm, @rd);
        insert into program values(instr_addr, @I_AUIPC, @rd, null, null,
            sign_extend(@imm, 32));
    elseif @opcode = 8 then -- SB, SH, SW
        call decode_S(instr, @imm, @rs2, @rs1, @funct3);
        if @funct3 = 0 then
            insert into program values(instr_addr, @I_SB, null, @rs1, @rs2,
                sign_extend(@imm, 12));
        elseif @funct3 = 1 then
            insert into program values(instr_addr, @I_SH, null, @rs1, @rs2,
                sign_extend(@imm, 12));
        elseif @funct3 = 2 then
            insert into program values(instr_addr, @I_SW, null, @rs1, @rs2,
                sign_extend(@imm, 12));
        else
            insert into program values(instr_addr, @I_ERROR, null, null, null,
                null);
        end if;
    elseif @opcode = 12 then -- ADD, SUB, SLL, SLT, SLTU, XOR, SRL, SRA, OR, AND
        call decode_R(instr, @funct7, @rs2, @rs1, @funct3, @rd);
        if @funct3 = 0 then
            if @funct7 = 0 then
                insert into program values(instr_addr, @I_ADD, @rd, @rs1, @rs2,
                    null);
            elseif @funct7 = 0x20 then
                insert into program values(instr_addr, @I_SUB, @rd, @rs1, @rs2,
                    null);
            else
                insert into program values(instr_addr, @I_ERROR, null, null,
                    null, null);
            end if;
        elseif @funct3 = 1 then
            insert into program values(instr_addr, @I_SLL, @rd, @rs1, @rs2,
                null);
        elseif @funct3 = 2 then
            insert into program values(instr_addr, @I_SLT, @rd, @rs1, @rs2,
                null);
        elseif @funct3 = 3 then
            insert into program values(instr_addr, @I_SLTU, @rd, @rs1, @rs2,
                null);
        elseif @funct3 = 4 then
            insert into program values(instr_addr, @I_XOR, @rd, @rs1, @rs2,
                null);
        elseif @funct3 = 5 then
            if @funct7 = 0 then
                insert into program values(instr_addr, @I_SRL, @rd, @rs1, @rs2,
                    null);
            elseif @funct7 = 0x20 then
                insert into program values(instr_addr, @I_SRA, @rd, @rs1, @rs2,
                    null);
            else
                insert into program values(instr_addr, @I_ERROR, null, null,
                    null, null);
            end if;
        elseif @funct3 = 6 then
            insert into program values(instr_addr, @I_OR, @rd, @rs1, @rs2,
                null);
        elseif @funct3 = 7 then
            insert into program values(instr_addr, @I_AND, @rd, @rs1, @rs2,
                null);
        else
            insert into program values(instr_addr, @I_ERROR, null, null, null,
                null);
        end if;
    elseif @opcode = 13 then -- LUI
        call decode_U(instr, @imm, @rd);
        insert into program values(instr_addr, @I_LUI, @rd, null, null, @imm);
    elseif @opcode = 24 then -- BEQ, BNE, BLT, BGE, BLTU, BGEU
        call decode_SB(instr, @imm, @rs2, @rs1, @funct3);
        if @funct3 = 0 then
            insert into program values(instr_addr, @I_BEQ, null, @rs1, @rs2,
                sign_extend(@imm, 13));
        elseif @funct3 = 1 then
            insert into program values(instr_addr, @I_BNE, null, @rs1, @rs2,
                sign_extend(@imm, 13));
        elseif @funct3 = 4 then
            insert into program values(instr_addr, @I_BLT, null, @rs1, @rs2,
                sign_extend(@imm, 13));
        elseif @funct3 = 5 then
            insert into program values(instr_addr, @I_BGE, null, @rs1, @rs2,
                sign_extend(@imm, 13));
        elseif @funct3 = 6 then
            insert into program values(instr_addr, @I_BLTU, null, @rs1, @rs2,
                sign_extend(@imm, 13));
        elseif @funct3 = 7 then
            insert into program values(instr_addr, @I_BGEU, null, @rs1, @rs2,
                sign_extend(@imm, 13));
        else
            insert into program values(instr_addr, @I_ERROR, null, null, null,
                null);
        end if;
    elseif @opcode = 25 then -- JALR
        call decode_I(instr, @imm, @rs1, @funct3, @rd);
        insert into program values(instr_addr, @I_JALR, @rd, @rs1, null,
            sign_extend(@imm, 12));
    elseif @opcode = 27 then -- JAL
        call decode_UJ(instr, @imm, @rd);
        insert into program values(instr_addr, @I_JAL, @rd, null, null, 
            sign_extend(@imm, 21));
    else
        insert into program values(instr_addr, @I_ERROR, null, null, null,
            null);
    end if;
end $$

-- ----------------------------------------------------------- --
-- Write a 32-bit register. Ignores write to the zero register --
-- ----------------------------------------------------------- --
create procedure write_reg(in rn int, in val long) not deterministic
begin
    if val < 0 then
        set val = signed_to_unsigned(val, 32);
    end if;
    if rn > 0 then
        replace into registers values (rn, val & 0xffffffff);
    end if;
end $$

-- ---------------------- --
-- Read a 32-bit register --
-- ---------------------- --
create function read_reg(rn int) returns long
begin
    set @tmp = (select reg_val from registers where reg_id = rn);
    if @tmp is not null then
        return @tmp;
    else
        return 0;
    end if;
end $$

-- ---------------------------------- --
-- Read an 8-bit byte from the memory --
-- ---------------------------------- --
create function mem_read_byte(addr long) returns long
begin
    set @tmp = (select mem_val from ram where mem_addr = addr);
    if @tmp is not null then
        return @tmp;
    else
        return 0;
    end if;
end $$

-- -------------------------------------- --
-- Read a 16-bit halfword from the memory --
-- -------------------------------------- --
create function mem_read_halfword(addr long) returns long
begin
    return mem_read_byte(addr) | (mem_read_byte(addr+1)<<8);
end $$

-- ---------------------------------- --
-- Read a 32-bit word from the memory --
-- ---------------------------------- --
create function mem_read_word(addr long) returns long
begin
    return ( mem_read_byte(addr) | (mem_read_byte(addr+1)<<8) |
        (mem_read_byte(addr+2)<<16) | (mem_read_byte(addr+3)<<24) );
end $$

-- --------------------------------- --
-- Write an 8-bit byte to the memory --
-- --------------------------------- --
create procedure mem_write_byte(in addr long, in val int unsigned)
    not deterministic
begin
    replace into ram values (addr, val & 0xff);
end $$

-- ------------------------------------- --
-- Write a 16-bit halfword to the memory --
-- ------------------------------------- --
create procedure mem_write_halfword(in addr long, in val int unsigned)
    not deterministic
begin
    call mem_write_byte(addr, val & 0xff);
    call mem_write_byte(addr+1, (val>>8) & 0xff);
end $$

-- --------------------------------- --
-- Write a 32-bit word to the memory --
-- --------------------------------- --
create procedure mem_write_word(in addr long, in val int unsigned)
    not deterministic
begin
    -- 0x87000000, the magic 'output address' featured on Yarvi
    if addr = 2264924160 then
        insert into output (msg) values (convert(val, char));
    else
        call mem_write_byte(addr, val & 0xff);
        call mem_write_byte(addr+1, (val>>8) & 0xff);
        call mem_write_byte(addr+2, (val>>16) & 0xff);
        call mem_write_byte(addr+3, (val>>24) & 0xff);
    end if;
end $$

-- -------------------------------------------- --
-- Execute the instruction at pc, and update pc --
-- -------------------------------------------- --
create procedure instr_execute(inout pc int) not deterministic
begin
    select operation, reg_d, reg_s1, reg_s2, immediate into
        @opcode, @rd, @rs1, @rs2, @imm from program where prog_addr = pc;
    if @opcode = @I_LB then
        call write_reg(@rd, sign_extend(mem_read_byte(
            read_reg(@rs1) + @imm), 8));
    elseif @opcode = @I_LH then
        call write_reg(@rd, sign_extend(mem_read_halfword(
            read_reg(@rs1) + @imm), 16));
    elseif @opcode = @I_LW then
        call write_reg(@rd, mem_read_word(read_reg(@rs1) + @imm));
    elseif @opcode = @I_LBU then
        call write_reg(@rd, mem_read_byte(read_reg(@rs1) + @imm));
    elseif @opcode = @I_LHU then
        call write_reg(@rd, mem_read_halfword(read_reg(@rs1) + @imm));
    elseif @opcode = @I_ADDI then
        call write_reg(@rd, read_reg(@rs1) + @imm);
    elseif @opcode = @I_SLTI then
        if sign_extend(read_reg(@rs1),32) < @imm then -- treat both as signed
            call write_reg(@rd, 1);
        else
            call write_reg(@rd, 0);
        end if;
    elseif @opcode = @I_SLTIU then
        if read_reg(@rs1) < signed_to_unsigned(@imm, 32) then -- both unsigned
            call write_reg(@rd, 1);
        else
            call write_reg(@rd, 0);
        end if;
    elseif @opcode = @I_XORI then
        call write_reg(@rd, (read_reg(@rs1) ^ @imm) & 0xffffffff);
    elseif @opcode = @I_ORI then
        call write_reg(@rd, (read_reg(@rs1) | @imm) & 0xffffffff);
    elseif @opcode = @I_ANDI then
        call write_reg(@rd, read_reg(@rs1) & @imm);
    elseif @opcode = @I_SLLI then
        call write_reg(@rd, read_reg(@rs1) << (@imm & 0x1f));
    elseif @opcode = @I_SRLI then
        call write_reg(@rd, read_reg(@rs1) >> (@imm & 0x1f));
    elseif @opcode = @I_SRAI then
        call write_reg(@rd, sign_extend(read_reg(@rs1) >> (@imm & 0x1f),
            32-(@imm & 0x1f)));
    elseif @opcode = @I_SB then
        call mem_write_byte(read_reg(@rs1) + @imm, read_reg(@rs2));
    elseif @opcode = @I_SH then
        call mem_write_halfword(read_reg(@rs1) + @imm, read_reg(@rs2));
    elseif @opcode = @I_SW then
        set @writeaddr = read_reg(@rs1) + @imm;
        call mem_write_word(@writeaddr, read_reg(@rs2));
    elseif @opcode = @I_BEQ then
        if read_reg(@rs1) = read_reg(@rs2) then
            set pc = pc + (@imm - 4);
        end if;
    elseif @opcode = @I_BNE then
        if read_reg(@rs1) != read_reg(@rs2) then
            set pc = pc + (@imm - 4);
        end if;
    elseif @opcode = @I_BLT then
        if sign_extend(read_reg(@rs1),32) < sign_extend(read_reg(@rs2),32) then
            set pc = pc + (@imm - 4);
        end if;
    elseif @opcode = @I_BGE then
        if sign_extend(read_reg(@rs1),32) >= sign_extend(read_reg(@rs2),32) then
            set pc = pc + (@imm - 4);
        end if;
    elseif @opcode = @I_BLTU then
        if read_reg(@rs1) < read_reg(@rs2) then
            set pc = pc + (@imm - 4);
        end if;
    elseif @opcode = @I_BGEU then
        if read_reg(@rs1) >= read_reg(@rs2) then
            set pc = pc + (@imm - 4);
        end if;
    elseif @opcode = @I_JALR then
        call write_reg(@rd, pc+4);
        set pc = read_reg(@rs1) + (@imm - 4);
    elseif @opcode = @I_AUIPC then
        call write_reg(@rd, pc + @imm);
    elseif @opcode = @I_LUI then
        call write_reg(@rd, @imm);
    elseif @opcode = @I_JAL then
        call write_reg(@rd, pc+4);
        set pc = pc + (@imm - 4);
    elseif @opcode = @I_ADD then
        call write_reg(@rd, read_reg(@rs1) + read_reg(@rs2));
    elseif @opcode = @I_SUB then
        call write_reg(@rd, read_reg(@rs1) - read_reg(@rs2));
    elseif @opcode = @I_SLL then
        call write_reg(@rd, read_reg(@rs1) << (read_reg(@rs2) & 0x1f));
    elseif @opcode = @I_SLT then
        if sign_extend(read_reg(@rs1),32) < sign_extend(read_reg(@rs2),32) then
            call write_reg(@rd, 1);
        else
            call write_reg(@rd, 0);
        end if;
    elseif @opcode = @I_SLTU then
        if read_reg(@rs1) < read_reg(@rs2) then
            call write_reg(@rd, 1);
        else
            call write_reg(@rd, 0);
        end if;
    elseif @opcode = @I_XOR then
        call write_reg(@rd, read_reg(@rs1) ^ read_reg(@rs2));
    elseif @opcode = @I_SRL then
        call write_reg(@rd, read_reg(@rs1) >> (read_reg(@rs2) & 0x1f));
    elseif @opcode = @I_SRA then
        call write_reg(@rd, sign_extend(read_reg(@rs1) >>
            (read_reg(@rs2) & 0x1f), 32-(read_reg(@rs2) & 0x1f)));
    elseif @opcode = @I_OR then
        call write_reg(@rd, read_reg(@rs1) | read_reg(@rs2));
    elseif @opcode = @I_AND then
        call write_reg(@rd, read_reg(@rs1) & read_reg(@rs2));
    elseif @opcode = @I_FENCE or @opcode = @I_FENCE_I then
        set pc = pc; -- nop
    else
        -- ERROR!
        set pc = -1;
    end if;
    set pc = pc + 4;
end $$

-- ------------------------------------------------------------------- --
-- Disassemble all the machine code instructions in the raw_prog table --
-- ------------------------------------------------------------------- --
create procedure decode_program() not deterministic
begin
    declare done int default false;
    declare paddr int;
    declare stinstr char(8);
    declare cur cursor for select prog_addr,str_instr from raw_prog;
    declare continue handler for not found set done = true;

    open cur;
    
    readloop: loop
        fetch cur into paddr, stinstr;
        if done then -- check if we've reached the end
            leave readloop;
        end if;
        call instr_decode((paddr-1)*4, conv(stinstr, 16, 10));
    end loop;
    
    close cur;
end $$

-- --------------------------------------------------------- --
-- Load all the static memory values from the raw_data table --
-- --------------------------------------------------------- --
create procedure load_mem() not deterministic
begin
    declare done int default false;
    declare paddr int;
    declare stdata char(8);
    declare cur cursor for select data_addr,str_data from raw_data;
    declare continue handler for not found set done = true;

    open cur;
    
    readloop: loop
        fetch cur into paddr, stdata;
        if done then -- check if we've reached the end
            leave readloop;
        end if;
        call mem_write_word((paddr-1)*4, conv(stdata, 16, 10));
    end loop;
    
    close cur;
end $$

-- ------------------------------------------------------------ --
-- Execute the code. Attempts to detect infinite loops and exit --
-- ------------------------------------------------------------ --
create function execute_program() returns int
begin
    set @pc = 0;
    set @oldpc = 1;
    -- Once the PC doesn't change, assume we're in an infinite spin, and exit
    while @oldpc != @pc do
        set @oldpc = @pc;
        call instr_execute(@pc);
    end while;
    return 0;
end $$

-- ------------------------------------------------------------------ --
-- Reset the memory, registers and output so we can run again cleanly --
-- ------------------------------------------------------------------ --
create procedure reset_emulator() not deterministic
begin
    -- We have to turn off safe_updates for this to work
    set SQL_SAFE_UPDATES = 0;
    delete from registers;
    delete from ram;
    delete from output;
    set SQL_SAFE_UPDATES = 1;
    
    call load_mem();
end $$

delimiter ;
