-- -- Author: Brent Seidel -- Date: 31-Jul-2024 -- -- This file is part of SimCPU. -- 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.Unchecked_Conversion; with Ada.Text_IO; with Ada.Text_IO.Unbounded_IO; with Ada.Strings.Unbounded; package body BBS.Sim_CPU.i8080 is -- function uint16_to_ctrl is new Ada.Unchecked_Conversion(source => uint16, target => ctrl_mode); function psw_to_byte is new Ada.Unchecked_Conversion(source => status_word, target => byte); function byte_to_psw is new Ada.Unchecked_Conversion(source => byte, target => status_word); function byte_to_op is new Ada.Unchecked_Conversion(source => byte, target => opcode); -- -- ---------------------------------------------------------------------- -- Simulator control -- -- Called once when Start/Stop switch is moved to start position -- overriding procedure start(self : in out i8080) is begin self.pc := self.addr; self.cpu_halt := False; self.lr_ctl.mode := PROC_KERN; end; -- -- Called to start simulator execution at a specific address. -- overriding procedure start(self : in out i8080; addr : addr_bus) is begin self.start; self.pc := word(addr and 16#FFFF#); end; -- -- Called once per frame when start/stop is in the start position and run/pause -- is in the run position. -- overriding procedure run(self : in out i8080) is begin if not self.halted then self.decode; end if; end; -- -- Called once when the Deposit switch is moved to the Deposit position. -- overriding procedure deposit(self : in out i8080) is begin if self.sr_ctl.addr then self.addr := word(self.sr_ad and 16#FFFF#); else self.mem(self.addr) := byte(self.sr_ad and 16#FF#); self.addr := self.addr + 1; end if; self.lr_addr := addr_bus(self.addr); self.lr_data := data_bus(self.mem(self.addr) and 16#FF#); end; -- -- Called once when the Examine switch is moved to the Examine position. -- overriding procedure examine(self : in out i8080) is begin self.lr_addr := addr_bus(self.addr); self.lr_data := data_bus(self.mem(self.addr)); self.addr := self.addr + 1; end; -- -- ---------------------------------------------------------------------- -- Simulator information -- -- Called first to initialize the simulator -- overriding procedure init(self : in out i8080) is begin self.addr := 0; self.temp_addr := 0; self.a := 0; self.b := 0; self.c := 0; self.d := 0; self.e := 0; self.h := 0; self.l := 0; self.sp := 0; self.pc := 0; self.psw.carry := False; self.psw.parity := False; self.psw.aux_carry := False; self.psw.zero := False; self.psw.sign := False; end; -- -- Called to get number of registers -- overriding function registers(self : in out i8080) return uint32 is pragma Unreferenced(self); begin return reg_id'Pos(reg_id'Last) + 1; end; -- -- Called to get variant name -- -- overriding -- function variant(self : in out i8080; v : natural) return String is -- begin -- return variants_i8080'Image(self.variant); -- end; -- -- Called to get current variant index -- overriding function variant(self : in out i8080) return Natural is begin return variants_i8080'pos(self.cpu_model); end; -- -- Called to set variant -- overriding procedure variant(self : in out i8080; v : natural) is begin self.cpu_model := variants_i8080'Val(v); end; -- -- ---------------------------------------------------------------------- -- Simulator data -- -- Called to set a memory value -- overriding procedure set_mem(self : in out i8080; mem_addr : addr_bus; data : data_bus) is begin self.mem(word(mem_addr and 16#FFFF#)) := byte(data and 16#FF#); end; -- -- Called to read a memory value -- overriding function read_mem(self : in out i8080; mem_addr : addr_bus) return data_bus is begin return data_bus(self.mem(word(mem_addr and 16#FFFF#))); end; -- -- Called to get register name -- overriding function reg_name(self : in out i8080; num : uint32) return String is pragma Unreferenced(self); begin if num <= reg_id'Pos(reg_id'Last) then return reg_id'Image(reg_id'Val(num)); else return "*invalid*"; end if; end; -- -- Called to get register value -- overriding function read_reg(self : in out i8080; num : uint32) return data_bus is reg : reg_id; begin if num <= reg_id'Pos(reg_id'Last) then reg := reg_id'Val(num); case reg is when reg_a => return data_bus(self.a); when reg_psw => return data_bus(psw_to_byte(self.psw)); when reg_b => return data_bus(self.b); when reg_c => return data_bus(self.c); when reg_bc => return data_bus(word(self.b)*16#100# + word(self.c)); when reg_d => return data_bus(self.d); when reg_e => return data_bus(self.e); when reg_de => return data_bus(word(self.d)*16#100# + word(self.e)); when reg_h => return data_bus(self.h); when reg_l => return data_bus(self.l); when reg_hl => return data_bus(word(self.h)*16#100# + word(self.l)); when reg_sp => return data_bus(self.sp); when reg_pc => return data_bus(self.pc); when others => return 0; end case; else return 0; end if; end; -- -- Called to get register value as a string (useful for flag registers) -- overriding function read_reg(self : in out i8080; num : uint32) return String is reg : reg_id; begin if num <= reg_id'Pos(reg_id'Last) then reg := reg_id'Val(num); case reg is when reg_a => return toHex(self.a); when reg_psw => return (if self.psw.sign then "S" else "-") & (if self.psw.zero then "Z" else "-") & "*" & (if self.psw.aux_carry then "A" else "-") & "*" & (if self.psw.parity then "P" else "-") & "*" & (if self.psw.carry then "C" else "-") & "(" & (if self.int_enable then "E" else "D") & ")"; when reg_b => return toHex(self.b); when reg_c => return toHex(self.c); when reg_bc => return toHex(word(self.b)*16#100# + word(self.c)); when reg_d => return toHex(self.d); when reg_e => return toHex(self.e); when reg_de => return toHex(word(self.d)*16#100# + word(self.e)); when reg_h => return toHex(self.h); when reg_l => return toHex(self.l); when reg_hl => return toHex(word(self.h)*16#100# + word(self.l)); when reg_sp => return toHex(self.sp); when reg_pc => return toHex(self.pc); when others => return "*invalid*"; end case; else return "*invalid*"; end if; end; -- -- Called to set register value -- -- overriding -- procedure set_reg(self : in out simple; num : uint32; -- data : uint32) is null; -- -- This loads data from a file specified by "name" into the simulator memory. -- Currently, only Intel Hex format is supported. -- overriding procedure load(self : in out i8080; name : String) is inp : Ada.Text_IO.File_Type; line : Ada.Strings.Unbounded.Unbounded_String; count : byte; addr : word; rec : byte; data : page; valid : Boolean; begin Ada.Text_IO.Open(inp, Ada.Text_IO.In_File, name); while not Ada.Text_IO.End_Of_File(inp) loop Ada.Text_IO.Unbounded_IO.Get_Line(inp, line); IntelHex(Ada.Strings.Unbounded.To_String(line), count, addr, rec, data, valid); exit when rec = 1; -- End of file record type if rec = 0 and valid then -- Process a data record for i in 0 .. count - 1 loop self.memory(addr + word(i), data(Integer(i)), ADDR_DATA); end loop; else Ada.Text_IO.Put_Line("Ignoring record: " & Ada.Strings.Unbounded.To_String(line)); end if; end loop; Ada.Text_IO.Close(inp); exception when Ada.Text_IO.Name_Error => Ada.Text_IO.Put_Line("Error in file name: " & name); when others => Ada.Text_IO.Put_Line("Error occured processing " & name); Ada.Text_IO.Close(inp); end; -- -- Called to check if the CPU is halted -- overriding function halted(self : in out i8080) return Boolean is begin return self.cpu_halt; end; -- -- This clears the halted flag allowing processing to continue. -- overriding procedure continue_proc(self : in out i8080) is begin self.cpu_halt := False; end; -- -- Interrupt status. Returns simulator dependent status of interrupts -- overriding function intStatus(self : in out i8080) return int32 is begin if self.int_enable then return 1; else return 0; end if; end; -- -- Input/Output debugging -- overriding function lastOutAddr(self : in out i8080) return addr_bus is begin return self.last_out_addr; end; -- overriding function lastOutData(self : in out i8080) return data_bus is begin return self.last_out_data; end; -- overriding procedure overrideIn(self : in out i8080; addr : in addr_bus; data : in data_bus) is begin self.in_override := True; self.in_over_addr := addr; self.in_over_data := data; end; -- -- Set and clear breakpoints. The implementation is up to the specific simulator. -- procedure setBreak(self : in out i8080; addr : addr_bus) is begin self.break_enable := True; self.break_point := word(addr and 16#FFFF#); end; -- procedure clearBreak(self : in out i8080; addr : addr_bus) is begin self.break_enable := False; end; -- -- Unimplemented instruction response -- -- Right now just print a message for unrecognized opcodes. -- At some point, may want to do something different here. -- procedure unimplemented(self : in out i8080; addr : word; data : byte) is begin Ada.Text_IO.Put_Line("Illegal instruction at " & ToHex(addr) & " code " & ToHex(data)); end; -- -------------------------------------------------------------------- -- -- Code for the instruction processing. -- -- Implementation matrix -- \ 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F -- 00 V V V V V V V V * V V V V V V V -- 10 * V V V V V V V * V V V V V V V -- 20 * V V V V V V V * V V V V V V V -- 30 * V V V V V V V * V V V V V V V -- 40 V V V V V V V V V V V V V V V V -- 50 V V V V V V V V V V V V V V V V -- 60 V V V V V V V V V V V V V V V V -- 70 V V V V V V V V V V V V V V V V -- 80 V V V V V V V V V V V V V V V V -- 90 V V V V V V V V V V V V V V V V -- A0 V V V V V V V V V V V V V V V V -- B0 V V V V V V V V V V V V V V V V -- C0 V V V V V V V V V V V * V V V V -- D0 V V V V V V V V V * V V V * V V -- E0 V V V V V V V V V V V V V * V V -- F0 V V V V V V V V V V V V V * V V -- -- * represents alternate opcodes that should not be used. -- X represents opcodes implemented. -- V represents opcodes implemented and tested. -- . or blank represent opcodes not yet implemented. procedure decode(self : in out i8080) is inst : byte; op_inst : opcode; reg1 : reg8_index; reg2 : reg8_index; reg16 : reg16_index; temp_addr : word; temp16 : word; temp8 : byte; begin self.intr := False; -- Currently interrupts are not implemented -- -- Check for breakpoint -- if self.break_enable then if self.break_point = self.pc then self.cpu_halt := True; if (word(self.trace) and 1) = 1 then Ada.Text_IO.Put_Line("TRACE: Breakpoint at " & toHex(self.pc)); end if; end if; end if; inst := self.get_next; op_inst := byte_to_op(inst); if (word(self.trace) and 1) = 1 then Ada.Text_IO.Put_Line("TRACE: Address: " & toHex(self.pc - 1) & " instruction " & toHex(inst) & " (" & opcode'Image(op_inst) & ")"); end if; -- -- Do instruction processing -- -- Note that 63 of the 256 instruction codes are occupied by various MOV -- instructions. The MOV M,M instruction is illegal and that code is used -- for the HLT instruction, hence the "inst /= 16#76#" test below. -- if ((inst and 16#C0#) = 16#40#) and (inst /= 16#76#) then -- An assortment of move instructions reg1 := inst and 16#07#; reg2 := (inst/8) and 16#07#; self.reg8(reg2, self.reg8(reg1)); else case inst is when 0 => -- NOP (No operation) null; when 16#01# |16#11# | 16#21# | 16#31# => -- LXI r (Load register pair) temp16 := word(self.get_next); temp16 := temp16 + word(self.get_next)*16#100#; self.reg16(reg16_index((inst and 16#30#)/16#10#), temp16, 0); when 16#02# | 16#12# => -- STAX B/D (Store accumulator at address) if inst = 16#02# then -- STAX B temp_addr := word(self.b)*16#100# + word(self.c); else temp_addr := word(self.d)*16#100# + word(self.e); end if; self.memory(temp_addr, self.a, ADDR_DATA); when 16#03# | 16#13# | 16#23# | 16#33# => -- INX r (increment double) reg16 := reg16_index((inst/16#10#) and 3); self.mod16(reg16, 1); when 16#04# |16#14# | 16#24# | 16#34# | 16#0C#| 16#1C# | 16#2C# | 16#3C# => -- INR r (Increment register) reg1 := (inst/8) and 7; self.mod8(reg1, 1); when 16#05# | 16#15# | 16#25# | 16#35# | 16#0D# | 16#1D# | 16#2D# | 16#3D# => -- DCR r (Decrement register) reg1 := (inst/8) and 7; self.mod8(reg1, -1); when 16#06# | 16#0E# | 16#16# | 16#1E# | 16#26# | 16#2E# | 16#36# | 16#3E# => -- MVI r (Move immediate to register) temp8 := self.get_next; self.reg8((inst and 16#38#)/16#08#, temp8); when 16#07# => -- RLC (Rotate accumulator left) temp16 := word(self.a)*2; if temp16 > 16#FF# then self.psw.carry := True; temp16 := temp16 + 1; else self.psw.carry := False; end if; self.a := byte(temp16 and 16#FF#); when 16#09# | 16#19# | 16#29# | 16#39# => -- DAD r (double add) reg16 := reg16_index((inst/16#10#) and 3); temp16 := self.dad(self.reg16(reg16_index(2), 0), self.reg16(reg16, 0)); self.reg16(reg16_index(2), temp16, 0); when 16#0A# | 16#1A# => -- LDAX B/D (Load accumulator from address) if inst = 16#0A# then -- LDAX B temp_addr := word(self.b)*16#100# + word(self.c); else -- LDAX D temp_addr := word(self.d)*16#100# + word(self.e); end if; self.a := self.memory(temp_addr, ADDR_DATA); when 16#0B# | 16#1B# | 16#2B# | 16#3B# => -- DCX r (decrement double) reg16 := reg16_index((inst/16#10#) and 3); self.mod16(reg16, -1); when 16#0F# => -- RRC (Rotate accumulator right) if (self.a and 16#01#) = 1 then self.psw.carry := True; else self.psw.carry := False; end if; self.a := self.a/2; if self.psw.carry then self.a := self.a + 16#80#; end if; when 16#17# => -- RAL (Rotate left through carry) temp16 := word(self.a)*2; if self.psw.carry then temp16 := temp16 + 1; end if; if temp16 > 16#FF# then self.psw.carry := True; else self.psw.carry := False; end if; self.a := byte(temp16 and 16#FF#); when 16#1F# => -- RAR (Rotate right through carry) temp16 := word(self.a); if self.psw.carry then temp16 := temp16 + 16#100#; end if; if (temp16 and 16#01#) = 1 then self.psw.carry := True; else self.psw.carry := False; end if; self.a := byte(temp16/2); when 16#20# => -- RIM (Read interrupt mask, 8085 only) -- -- This will need to be updated once interrupts are -- implemented. It will also need to be updated should -- the serial input ever be implemented. Right now, all -- it does is return the status of the interrupt enable -- flag. -- -- Note that for the Z80, this is a JR NZ,d instruction -- if self.cpu_model = var_8085 then if self.int_enable then self.a := 16#08#; else self.a := 16#00#; end if; else self.unimplemented(self.pc, inst); end if; when 16#22# => -- SHLD addr (Store HL direct) temp_addr := word(self.get_next); temp_addr := temp_addr + word(self.get_next)*16#100#; self.memory(temp_addr, self.l, ADDR_DATA); temp_addr := temp_addr + 1; self.memory(temp_addr, self.h, ADDR_DATA); when 16#27# => -- DAA (Decimal adjust accumulator) temp8 := self.a; if ((temp8 and 16#0F#) > 6) or self.psw.aux_carry then if ((temp8 and 16#0F#) + 6) > 16#0F# then self.psw.aux_carry := True; else self.psw.aux_carry := False; end if; temp8 := temp8 + 6; end if; if ((temp8/16#10# and 16#0F#) > 6) or self.psw.carry then if ((temp8/16#10# and 16#0F#) + 6) > 16#0F# then self.psw.carry := True; else self.psw.carry := False; end if; temp8 := temp8 + 16#60#; end if; self.a := temp8; when 16#2A# => -- LHLD addr (Load HL direct) temp_addr := word(self.get_next); temp_addr := temp_addr + word(self.get_next)*16#100#; self.l := self.memory(temp_addr, ADDR_DATA); temp_addr := temp_addr + 1; self.h := self.memory(temp_addr, ADDR_DATA); when 16#2F# => -- CMA (Complement accumulator) self.a := not self.a; when 16#30# => -- SIM (Set interrupt mask, 8085 only) -- -- This will need to be updated once interrupts are -- implemented. It will also need to be updated should -- the serial input ever be implemented. Right now, -- this instruction does nothing. -- -- Note that for the Z80, this is a JR NC,d instruction -- if self.cpu_model = var_8085 then null; else self.unimplemented(self.pc, inst); end if; when 16#32# => -- STA addr (Store accumulator) temp_addr := word(self.get_next); temp_addr := temp_addr + word(self.get_next)*16#100#; self.memory(temp_addr, self.a, ADDR_DATA); when 16#37# => -- STC (Set carry) self.psw.carry := True; when 16#3A# => -- LDA addr (Load accumulator) temp_addr := word(self.get_next); temp_addr := temp_addr + word(self.get_next)*16#100#; self.a := self.memory(temp_addr, ADDR_DATA); when 16#3F# => -- CMC (Complement carry) self.psw.carry := not self.psw.carry; when 16#76# => -- HLT (Halt) self.cpu_halt := True; when 16#80# | 16#81# | 16#82# | 16#83# |16#84# | 16#85# | 16#86# | 16#87# => -- ADD r (ADD register to accumulator) reg1 := inst and 16#07#; self.a := self.addf(self.a, self.reg8(reg1), False); when 16#88# | 16#89# | 16#8A# | 16#8B# |16#8C# | 16#8D# | 16#8E# | 16#8F# => -- ADC r (ADD register to accumulator with carry) reg1 := inst and 16#07#; self.a := self.addf(self.a, self.reg8(reg1), self.psw.carry); when 16#90# | 16#91# | 16#92# | 16#93# |16#94# | 16#95# | 16#96# | 16#97# => -- SUB r (SUB register from accumulator) reg1 := inst and 16#07#; self.a := self.subf(self.a, self.reg8(reg1), False); when 16#98# | 16#99# | 16#9A# | 16#9B# |16#9C# | 16#9D# | 16#9E# | 16#9F# => -- SBB r (SUB register from accumulator with borrow) reg1 := inst and 16#07#; self.a := self.subf(self.a, self.reg8(reg1), self.psw.carry); when 16#A0# | 16#A1# | 16#A2# | 16#A3# |16#A4# | 16#A5# | 16#A6# | 16#A7# => -- ANA r (AND accumulator with register) reg1 := inst and 16#07#; self.a := self.a and self.reg8(reg1); self.psw.carry := False; self.setf(self.a); when 16#A8# | 16#A9# | 16#AA# | 16#AB# |16#AC# | 16#AD# | 16#AE# | 16#AF# => -- XRA r (XOR accumulator with register) reg1 := inst and 16#07#; self.a := self.a xor self.reg8(reg1); self.psw.carry := False; self.setf(self.a); when 16#B0# | 16#B1# | 16#B2# | 16#B3# |16#B4# | 16#B5# | 16#B6# | 16#B7# => -- ORA r (OR accumulator with register) reg1 := inst and 16#07#; self.a := self.a or self.reg8(reg1); self.psw.carry := False; self.setf(self.a); when 16#B8# | 16#B9# | 16#BA# | 16#BB# |16#BC# | 16#BD# | 16#BE# | 16#BF# => -- CMP r (CMP register with accumulator) reg1 := inst and 16#07#; -- Only intersted in flags. Ignore the actual result. temp8 := self.subf(self.a, self.reg8(reg1), False); when 16#C0# => -- RNZ (Return if not zero) self.ret(not self.psw.zero); when 16#C1# | 16#D1# | 16#E1# | 16#F1# => -- POP r (Pop from stack) temp16 := word(self.memory(self.sp, ADDR_DATA)); self.sp := self.sp + 1; temp16 := temp16 + word(self.memory(self.sp, ADDR_DATA))*16#100#; self.sp := self.sp + 1; self.reg16(reg16_index((inst and 16#30#)/16#10#), temp16, 1); when 16#C2# => -- JNZ (Jump if not zero) self.jump(not self.psw.zero); when 16#C3# => -- JMP (Jump unconditional) self.jump(true); when 16#C4# => -- CNZ (Call if not zero) self.call(not self.psw.zero); when 16#C5# | 16#D5# | 16#E5# | 16#F5# => -- PUSH r (Push to stack) temp16 := self.reg16(reg16_index((inst and 16#30#)/16#10#), 1); self.sp := self.sp - 1; self.memory(self.sp, byte(temp16/16#100#), ADDR_DATA); self.sp := self.sp - 1; self.memory(self.sp, byte(temp16 and 16#FF#), ADDR_DATA); when 16#C6# => -- ADI (ADD immediate with accumulator) reg1 := inst and 16#07#; self.a := self.addf(self.a, self.get_next, False); when 16#C7# | 16#CF# | 16#D7# | 16#DF# | 16#E7# | 16#EF# | 16#F7# | 16#FF# => -- RST n (Restart) temp8 := (inst/16#8#) and 7; temp16 := self.pc; self.sp := self.sp - 1; self.memory(self.sp, byte(temp16/16#100#), ADDR_DATA); self.sp := self.sp - 1; self.memory(self.sp, byte(temp16 and 16#FF#), ADDR_DATA); self.pc := word(temp8*16#08#); when 16#C8# => -- RZ (Return if zero) self.ret(self.psw.zero); when 16#C9# => -- RET (Return unconditional) self.ret(True); when 16#CA# => -- JZ (Jump if zero) self.jump(self.psw.zero); when 16#CC# => -- CZ (Call if zero) self.call(self.psw.zero); when 16#CD# => -- CALL (Call unconditional) self.call(True); when 16#CE# => -- ACI (ADD immediate with accumulator and carry) reg1 := inst and 16#07#; self.a := self.addf(self.a, self.get_next, self.psw.carry); when 16#D0# => -- RNZ (Return if not carry) self.ret(not self.psw.carry); when 16#D2# => -- JNC (Jump if not carry) self.jump(not self.psw.carry); when 16#D3# => -- OUT (Output to port) temp8 := self.get_next; self.port(temp8, self.a); self.last_out_addr := addr_bus(temp8); self.last_out_data := data_bus(self.a); when 16#D4# => -- CNC (Call if not carry) self.call(not self.psw.carry); when 16#D6# => -- SUI (Subtract immediate) self.a := self.subf(self.a, self.get_next, False); when 16#D8# => -- RC (Return if carry) self.ret(self.psw.carry); when 16#DA# => -- JC (Jump if carry) self.jump(self.psw.carry); when 16#DB# => -- IN (Input from port) temp8 := self.get_next; if self.in_override and (addr_bus(temp8) = self.in_over_addr) then self.a := byte(self.in_over_data and 16#FF#); else self.a := self.port(temp8); end if; self.in_override := False; when 16#DC# => -- CC (Call if carry) self.call(self.psw.carry); when 16#DE# => -- SUI (Subtract immediate) self.a := self.subf(self.a, self.get_next, self.psw.carry); when 16#E0# => -- RNZ (Return if parity odd (parity flag false)) self.ret(not self.psw.parity); when 16#E2# => -- JPO (Jump if parity odd (parity flag false)) self.jump(not self.psw.parity); when 16#E3# => -- XTHL (Exchange HL with top of stack) temp8 := self.memory(self.sp, ADDR_DATA); self.memory(self.sp, self.l, ADDR_DATA); self.l := temp8; temp8 := self.memory(self.sp + 1, ADDR_DATA); self.memory(self.sp + 1, self.h, ADDR_DATA); self.h := temp8; when 16#E4# => -- CPO (Call if parity odd (parity flag false)) self.call(not self.psw.parity); when 16#E6# => -- ANI (AND immediate with accumulator) self.a := self.a and self.get_next; self.psw.carry := False; self.setf(self.a); when 16#E8# => -- RPE (Return if parity even (parity flag true)) self.ret(self.psw.parity); when 16#E9# => -- PCHL (Copies HL into PC) self.pc := word(self.h)*16#100# + word(self.l); when 16#EA# => -- JPE (Jump if parity even (parity flag true)) self.jump(self.psw.parity); when 16#EB# => -- XCHG (Exchange HL and DE registers) temp8 := self.d; self.d := self.h; self.h := temp8; temp8 := self.e; self.e := self.l; self.l := temp8; when 16#EC# => -- CPE (Call if parity even (parity flag true)) self.call(self.psw.parity); when 16#EE# => -- XRI (Exclusive OR immediate with accumulator) self.a := self.a xor self.get_next; self.psw.carry := False; self.setf(self.a); when 16#F0# => -- RP (Return if positive (sign flag false)) self.ret(not self.psw.sign); when 16#F2# => -- JP (Jump if positive (sign flag false)) self.jump(not self.psw.sign); when 16#F3# | 16#FB# => -- DI and EI (disable/enable interrupts) self.int_enable := (inst = 16#FB#); when 16#F4# => -- CP (Call if positive (sign flag false)) self.call(not self.psw.sign); when 16#F6# => -- ORI (OR immediate with accumulator) self.a := self.a or self.get_next; self.psw.carry := False; self.setf(self.a); when 16#F8# => -- RM (Return if minus (sign flag true)) self.ret(self.psw.sign); when 16#F9# => -- SPHL (Copies HL into SP) self.sp := word(self.h)*16#100# + word(self.l); when 16#FA# => -- JM (Jump if minus (sign flag true)) self.jump(self.psw.sign); when 16#FC# => -- CM (Call if minus (sign flag true)) self.call(self.psw.sign); when 16#FE# => -- CPI (Compare immediate) temp8 := self.subf(self.a, self.get_next, False); when others => self.unimplemented(self.pc, inst); end case; end if; end; -- -- Utility code for instruction decoder -- function get_next(self : in out i8080) return byte is t : byte; begin self.lr_ctl.atype := ADDR_INST; if self.intr then self.lr_ctl.atype := ADDR_INTR; return 0; -- Currently interrupts are not implemented else t := self.memory(self.pc, ADDR_INST); self.pc := self.pc + 1; return t; end if; end; -- procedure reg8(self : in out i8080; reg : reg8_index; value : byte) is begin case reg is when 0 => self.b := value; when 1 => self.c := value; when 2 => self.d := value; when 3 => self.e := value; when 4 => self.h := value; when 5 => self.l := value; when 6 => -- Memory self.memory(word(self.h)*16#100# + word(self.l), value, ADDR_DATA); when 7 => self.a := value; when others => null; end case; end; -- function reg8(self : in out i8080; reg : reg8_index) return byte is begin case reg is when 0 => return self.b; when 1 => return self.c; when 2 => return self.d; when 3 => return self.e; when 4 => return self.h; when 5 => return self.l; when 6 => return self.memory(word(self.h)*16#100# + word(self.l), ADDR_DATA); when 7 => return self.a; when others => return 0; end case; end; -- procedure reg16(self : in out i8080; reg : reg16_index; value : word; v : Natural) is begin case reg is when 0 => -- Register pair BC self.b := byte(value/16#100#); self.c := byte(value and 16#FF#); when 1 => -- Register pair DE self.d := byte(value/16#100#); self.e := byte(value and 16#FF#); when 2 => -- Register pair HL self.h := byte(value/16#100#); self.l := byte(value and 16#FF#); when 3 => -- Register pair A and PSW if v = 0 then self.sp := value; elsif v = 1 then self.a := byte(value/16#100#); self.psw := byte_to_psw(byte(value and 16#FF#)); end if; when others => null; end case; end; -- function reg16(self : in out i8080; reg : reg16_index; v : Natural) return word is begin case reg is when 0 => -- Register pair BC return word(self.b)*16#100# + word(self.c); when 1 => -- Register pair DE return word(self.d)*16#100# + word(self.e); when 2 => -- Register pair HL return word(self.h)*16#100# + word(self.l); when 3 => -- Register pair A and PSW if v = 0 then return self.sp; else return word(self.a)*16#100# + word(psw_to_byte(self.psw)); end if; when others => return 0; end case; end; -- -- Set flags based on value (zero, sign, parity) -- procedure setf(self : in out i8080; value : byte) is p : byte := 0; -- Bit counter begin self.psw.zero := (value = 0); self.psw.sign := ((value and 16#80#) = 16#80#); p := p + (if ((value and 16#01#) = 16#01#) then 1 else 0); p := p + (if ((value and 16#02#) = 16#02#) then 1 else 0); p := p + (if ((value and 16#04#) = 16#04#) then 1 else 0); p := p + (if ((value and 16#08#) = 16#08#) then 1 else 0); p := p + (if ((value and 16#10#) = 16#10#) then 1 else 0); p := p + (if ((value and 16#20#) = 16#20#) then 1 else 0); p := p + (if ((value and 16#40#) = 16#40#) then 1 else 0); p := p + (if ((value and 16#80#) = 16#80#) then 1 else 0); self.psw.parity := not ((p and 16#01#) = 16#01#); -- True is even parity end; -- -- Perform addition and set flags including carry and aux carry -- function addf(self : in out i8080; v1 : byte; v2 : byte; c : Boolean) return byte is sum : word; temp : byte; begin sum := word(v1) + word(v2); if c then sum := sum + 1; end if; if sum > 16#FF# then self.psw.carry := True; else self.psw.carry := False; end if; temp := (v1 and 16#0F#) + (v2 and 16#0F#); if c then temp := temp + 1; end if; if temp > 16#0F# then self.psw.aux_carry := True; else self.psw.aux_carry := False; end if; self.setf(byte(sum and 16#FF#)); return byte(sum and 16#FF#); end; -- -- Perform subtraction and set flags including carry and aux carry -- function subf(self : in out i8080; v1 : byte; v2 : byte; c : Boolean) return byte is diff : word; temp : byte; begin diff := word(v1) - word(v2); if c then diff := diff - 1; end if; if diff > 16#FF# then self.psw.carry := True; else self.psw.carry := False; end if; temp := (v1 and 16#0F#) - (v2 and 16#0F#); if c then temp := temp - 1; end if; if temp > 16#0F# then self.psw.aux_carry := True; else self.psw.aux_carry := False; end if; self.setf(byte(diff and 16#FF#)); return byte(diff and 16#FF#); end; -- -- Modify a single 8 bit value. This is used for both incrememnt and decrement. -- Flags are affected. -- procedure mod8(self : in out i8080; reg : reg8_index; dir : Integer) is value : byte; begin case reg is when 0 => value := self.b; when 1 => value := self.c; when 2 => value := self.d; when 3 => value := self.e; when 4 => value := self.h; when 5 => value := self.l; when 6 => value := self.memory(word(self.h)*16#100# + word(self.l), ADDR_DATA); when 7 => value := self.a; when others => value := 0; end case; if dir < 0 then if (value and 16#0F#) - 1 > 16#0F# then self.psw.aux_carry := True; else self.psw.aux_carry := False; end if; value := value - 1; else if (value and 16#0F#) + 1 > 16#0F# then self.psw.aux_carry := True; else self.psw.aux_carry := False; end if; value := value + 1; end if; self.setf(value); case reg is when 0 => self.b := value; when 1 => self.c := value; when 2 => self.d := value; when 3 => self.e := value; when 4 => self.h := value; when 5 => self.l := value; when 6 => -- Memory self.memory(word(self.h)*16#100# + word(self.l), value, ADDR_DATA); when 7 => self.a := value; when others => null; end case; end; -- -- Perform addition of register pairs. The carry flag is affected -- function dad(self : in out i8080; v1 : word; v2 : word) return word is sum : uint32; begin sum := uint32(v1) + uint32(v2); if (sum and 16#FFFF0000#) /= 0 then self.psw.carry := True; else self.psw.carry := False; end if; return word(sum and 16#FFFF#); end; -- -- Modify a 16 bit register pair. This is used for both increment and decrement. -- No flags are affected. Dir is positive for increment and negative for -- decrement. -- procedure mod16(self : in out i8080; reg : reg16_index; dir : Integer) is value : word; begin case reg is when 0 => -- Register pair BC value := word(self.b)*16#100# + word(self.c); when 1 => -- Register pair DE value := word(self.d)*16#100# + word(self.e); when 2 => -- Register pair HL value := word(self.h)*16#100# + word(self.l); when 3 => -- Register pair A and PSW value := self.sp; when others => value := 0; end case; if dir < 0 then value := value - 1; else value := value + 1; end if; case reg is when 0 => -- Register pair BC self.b := byte(value/16#100#); self.c := byte(value and 16#FF#); when 1 => -- Register pair DE self.d := byte(value/16#100#); self.e := byte(value and 16#FF#); when 2 => -- Register pair HL self.h := byte(value/16#100#); self.l := byte(value and 16#FF#); when 3 => -- Register pair A and PSW self.sp := value; when others => null; end case; end; -- -- All memory accesses should be routed through these functions so that they -- can do checks for memory-mapped I/O or shared memory. -- procedure memory(self : in out i8080; addr : word; value : byte; mode : addr_type) is begin -- -- Set LED register values -- self.lr_addr := addr_bus(addr); self.lr_data := data_bus(value); self.lr_ctl.atype := mode; -- -- Set memory. Optionally, checks for memory mapped I/O or shared memory -- or other special stuff can be added here. -- self.mem(addr) := value; end; -- function memory(self : in out i8080; addr : word; mode : addr_type) return byte is begin -- -- Set LED register values -- self.lr_addr := addr_bus(addr); self.lr_data := data_bus(self.mem(addr)); self.lr_ctl.atype := mode; -- -- Read memory. Optionally, checks for memory mapped I/O or shared memory -- or other special stuff can be added here. -- return self.mem(addr); end; -- -- Called to attach an I/O device to a simulator at a specific address. Bus -- is simulator dependent as some CPUs have separate I/O and memory space. -- For bus: -- 0 - I/O space -- 1 - Memory space (currently unimplemented) -- overriding procedure attach_io(self : in out i8080; io_dev : io_access; base_addr : addr_bus; bus : bus_type) is size : addr_bus := io_dev.all.getSize; valid : Boolean := True; begin if bus = BUS_IO then -- -- Check for port conflicts -- for i in uint8(base_addr) .. uint8(base_addr + size - 1) loop if self.io_ports(i) /= null then valid := False; Ada.Text_IO.Put_Line("Port conflict detected attaching device to port " & toHex(i)); end if; exit when not valid; end loop; if valid then for i in uint8(base_addr) .. uint8(base_addr + size - 1) loop self.io_ports(i) := io_dev; Ada.Text_IO.Put_Line("Attaching " & io_dev.name & " to I/O port " & toHex(i)); end loop; io_dev.setBase(base_addr); end if; elsif bus = BUS_MEMORY then Ada.Text_IO.Put_Line("Memory mapped I/O not yet implemented"); else Ada.Text_IO.Put_Line("Unknown I/O bus type"); end if; end; -- -- Handle I/O port accesses -- procedure port(self : in out i8080; addr : byte; value : byte) is begin if self.io_ports(addr) /= null then self.io_ports(addr).all.write(addr_bus(addr), data_bus(value)); if (word(self.trace) and 2) = 2 then Ada.Text_IO.Put_Line("Output " & toHex(value) & " to port " & toHex(addr)); end if; else Ada.Text_IO.Put_Line("Output " & toHex(value) & " to unassigned port " & toHex(addr)); end if; end; -- function port(self : in out i8080; addr : byte) return byte is begin if self.io_ports(addr) /= null then if (word(self.trace) and 2) = 2 then Ada.Text_IO.Put_Line("Input from port " & toHex(addr)); end if; return byte(self.io_ports(addr).all.read(addr_bus(addr)) and 16#FF#); end if; Ada.Text_IO.Put_Line("Input from unassigned port " & toHex(addr)); return addr; end; -- -- Common code for Jump, Call, and Return -- procedure jump(self : in out i8080; go : Boolean) is temp_pc : word; begin if go then temp_pc := word(self.get_next); temp_pc := temp_pc + word(self.get_next)*16#100#; self.pc := temp_pc; else self.pc := self.pc + 2; end if; end; -- procedure call(self : in out i8080; go : Boolean) is temp_pc : word; begin if go then temp_pc := word(self.get_next); temp_pc := temp_pc + word(self.get_next)*16#100#; -- self.sp := self.sp - 1; self.memory(self.sp, byte(self.pc/16#100#), ADDR_DATA); self.sp := self.sp - 1; self.memory(self.sp, byte(self.pc and 16#FF#), ADDR_DATA); self.pc := temp_pc; else self.pc := self.pc + 2; end if; end; -- procedure ret(self : in out i8080; go : Boolean) is temp_pc : word; begin if go then temp_pc := word(self.memory(self.sp, ADDR_DATA)); self.sp := self.sp + 1; temp_pc := temp_pc + word(self.memory(self.sp, ADDR_DATA))*16#100#; self.sp := self.sp + 1; self.pc := temp_pc; end if; end; -- end BBS.Sim_CPU.i8080;