--
-- Author: Brent Seidel
-- Date: 31-Jul-2024
--
-- This file is part of SimCPU CLI.
-- SimCPU is free software: you can redistribute it and/or modify it
-- under the terms of the GNU General Public License as published by the
-- Free Software Foundation, either version 3 of the License, or (at your
-- option) any later version.
--
-- SimCPU is distributed in the hope that it will be useful, but
-- WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
-- Public License for more details.
--
-- You should have received a copy of the GNU General Public License along
-- with SimCPU. If not, see .--
--
with Ada.Text_IO;
with Ada.Text_IO.Unbounded_IO;
with Ada.Strings.Unbounded;
use type Ada.Strings.Unbounded.Unbounded_String;
with Ada.Strings.Maps.Constants;
with BBS;
use type BBS.uint8;
use type BBS.uint32;
with BBS.lisp;
with BBS.Sim_CPU.Lisp;
package body test_util is
--
-- Dump registers
--
procedure dump_reg(c : BBS.Sim_CPU.simulator'Class) is
regs : BBS.uint32 := cpu.registers;
begin
for i in 0 .. (regs - 1) loop
Ada.Text_IO.Put("Reg " & BBS.uint32'Image(i) & " - ");
Ada.Text_IO.put(CPU.reg_name(i) & " = ");
Ada.Text_IO.Put_Line(CPU.read_reg(i));
end loop;
end;
--
procedure New_Line is
begin
Ada.Text_IO.New_Line;
end;
--
-- Command loop. The supported commands are:
-- BREAK
-- Set a breakpoint (currently only one can be active at a time)
-- CONTINUE
-- Continue execution
-- DEP
-- Deposit value to a memory location
-- DUMP
-- Display a region of memory
-- EXIT
-- EXIT the program
-- GO
-- Start execution at a specified address
-- LISP
-- Enter Lisp interpreter
-- LOAD
-- Load data from a file into memory
-- QUIT
-- Synonym for EXIT
-- REG
-- Display register values
-- RUN
-- Execute instructions until halt or breakpoint
-- STEP
-- Execute one instruction
-- TRACE
-- Print information for each instruction executed
-- UNBREAK
-- Remove a breakpoint
--
procedure cmds is
cmd : Ada.Strings.Unbounded.Unbounded_String;
first : Ada.Strings.Unbounded.Unbounded_String;
rest : Ada.Strings.Unbounded.Unbounded_String;
index : Natural;
addr : BBS.uint32;
value : BBS.uint32;
level : BBS.uint32;
exit_flag : Boolean := False;
char : Character;
available : Boolean;
interrupt : Character := Character'Val(5); -- Control-E
begin
BBS.lisp.init(Ada.Text_IO.Put_Line'Access, Ada.Text_IO.Put'Access,
New_Line'Access, Ada.Text_IO.Get_Line'Access);
BBS.Sim_CPU.Lisp.init(cpu);
loop
Ada.Text_IO.Put("CMD>");
Ada.Text_IO.Unbounded_IO.Get_Line(cmd);
--
-- Discard any leading spaces
--
index := Ada.Strings.Unbounded.Index_Non_Blank(cmd, 1);
cmd := Ada.Strings.Unbounded.Unbounded_Slice(cmd, index, Ada.Strings.Unbounded.Length(cmd));
--
-- Split into command and the rest of the string
--
index := Ada.Strings.Unbounded.Index(cmd, " ");
if index = 0 then
first := cmd;
rest := Ada.Strings.Unbounded.Null_Unbounded_String;
else
first := Ada.Strings.Unbounded.Unbounded_Slice(cmd, 1, index - 1);
rest := Ada.Strings.Unbounded.Unbounded_Slice(cmd, index + 1,
Ada.Strings.Unbounded.Length(cmd));
end if;
--
-- Command to uppercase
--
Ada.Strings.Unbounded.Translate(first, Ada.Strings.Maps.Constants.Upper_Case_Map);
--
-- Interpret the command
--
if first = ";" then
Ada.Text_IO.Put_Line(Ada.Strings.Unbounded.To_String(rest));
elsif Ada.Strings.Unbounded.Length(first) = 0 then
null; -- Ignore blank lines
elsif first = "S" or first = "STEP" then
cpu.run;
if cpu.halted then
Ada.Text_IO.Put_Line("CPU is halted");
else
dump_reg(cpu.all);
end if;
elsif first = "R" or first = "RUN" then
while not cpu.halted loop
cpu.run;
--
-- On Windows using the git bash shell, this seems to
-- wait for a character to be available rather than
-- checking is a character is available.
--
if not gitbash then
Ada.Text_IO.Get_Immediate(char, available);
exit when available and then char = interrupt;
end if;
end loop;
if not gitbash then
if available and char = interrupt then
Ada.Text_IO.Put_Line("User requested break");
else
Ada.Text_IO.Put_Line("CPU Halted");
end if;
end if;
dump_reg(cpu.all);
elsif first = "REG" then
dump_reg(cpu.all);
elsif first = "DEP" then
Ada.Strings.Unbounded.Translate(rest, Ada.Strings.Maps.Constants.Upper_Case_Map);
nextValue(addr, rest);
nextValue(value, rest);
CPU.set_mem(addr, value);
elsif first = "TRACE" then
Ada.Strings.Unbounded.Translate(rest, Ada.Strings.Maps.Constants.Upper_Case_Map);
nextValue(level, rest);
CPU.trace(Natural(level));
elsif first = "D" or first = "DUMP" then
Ada.Strings.Unbounded.Translate(rest, Ada.Strings.Maps.Constants.Upper_Case_Map);
nextValue(addr, rest);
dump_mem(addr);
elsif first = "GO" then
Ada.Strings.Unbounded.Translate(rest, Ada.Strings.Maps.Constants.Upper_Case_Map);
nextValue(addr, rest);
CPU.start(addr);
elsif first = "LOAD" then
Ada.Text_IO.Put_Line("Loading " & Ada.Strings.Unbounded.To_String(rest));
CPU.load(Ada.Strings.Unbounded.To_String(rest));
elsif first = "LISP" then
BBS.lisp.repl;
elsif first = "CONTINUE" then
CPU.continue_proc;
elsif first = "BREAK" then
nextValue(addr, rest);
CPU.setBreak(addr);
elsif first = "UNBREAK" then
nextValue(addr, rest);
CPU.clearBreak(addr);
elsif first = "QUIT" or first = "EXIT" then
exit_flag := True;
elsif first = "INT" or first = "INTERRUPT" then
index := ada.Strings.Unbounded.Index(rest, " ");
if index = 0 then
first := rest;
rest := Ada.Strings.Unbounded.Null_Unbounded_String;
else
first := Ada.Strings.Unbounded.Unbounded_Slice(rest, 1, index - 1);
rest := Ada.Strings.Unbounded.Unbounded_Slice(rest, index + 1,
Ada.Strings.Unbounded.Length(rest));
end if;
if first = "ON" then
cpu.interrupts(True);
elsif first = "OFF" then
cpu.interrupts(False);
elsif first = "SEND" then
nextValue(addr, rest);
cpu.interrupt(addr);
else
Ada.Text_IO.Put_Line("Unrecognized option to interrupt command <" & Ada.Strings.Unbounded.To_String(first) &
">");
end if;
elsif first = "RESET" then
CPU.init;
else
Ada.Text_IO.Put_Line("Unrecognized command <" & Ada.Strings.Unbounded.To_String(first) & ">");
end if;
exit when exit_flag;
end loop;
end;
--
-- Pull the next hexidecimal value off of a string
--
procedure nextValue(v : out BBS.uint32;
s : in out Ada.Strings.Unbounded.Unbounded_String) is
first : Ada.Strings.Unbounded.Unbounded_String;
rest : Ada.Strings.Unbounded.Unbounded_String;
index : Natural;
begin
index := ada.Strings.Unbounded.Index(s, " ");
if index = 0 then
first := s;
rest := Ada.Strings.Unbounded.Null_Unbounded_String;
else
first := Ada.Strings.Unbounded.Unbounded_Slice(s, 1, index - 1);
rest := Ada.Strings.Unbounded.Unbounded_Slice(s, index + 1,
Ada.Strings.Unbounded.Length(s));
end if;
v := BBS.Sim_CPU.toHex(Ada.Strings.Unbounded.To_String(first));
s := rest;
end;
--
-- Memory
--
procedure dump_mem(start : BBS.Sim_CPU.addr_bus) is
addr : BBS.Sim_CPU.addr_bus := start;
temp : BBS.Sim_CPU.byte;
begin
Ada.Text_IO.Put(" ");
for i in 0 .. 15 loop
Ada.Text_IO.Put(" " & BBS.Sim_CPU.toHex(BBS.Sim_CPU.byte(i)));
end loop;
Ada.Text_IO.New_Line;
for i in 0 .. 15 loop
Ada.Text_IO.Put(BBS.Sim_CPU.toHex(addr) & " :");
for j in 0 .. 15 loop
Ada.Text_IO.Put(" " & BBS.Sim_CPU.toHex(BBS.Sim_CPU.byte(CPU.read_mem(addr + BBS.Sim_CPU.addr_bus(j)))));
end loop;
Ada.Text_IO.Put(" ");
for j in 0 .. 15 loop
temp := BBS.Sim_CPU.byte(CPU.read_mem(addr + BBS.Sim_CPU.addr_bus(j)));
if (temp < 32) or (temp > 126) then
Ada.Text_IO.Put(".");
else
Ada.Text_IO.Put(Character'Val(temp));
end if;
end loop;
addr := addr + 16;
Ada.Text_IO.New_Line;
end loop;
end;
--
end test_util;