avruby 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +28 -0
  3. data/README.md +17 -0
  4. data/bin/avruby_shell +162 -0
  5. data/lib/avr.rb +41 -0
  6. data/lib/avr/argument.rb +23 -0
  7. data/lib/avr/clock.rb +99 -0
  8. data/lib/avr/cpu.rb +254 -0
  9. data/lib/avr/device.rb +249 -0
  10. data/lib/avr/device/atmel_atmega328p.rb +181 -0
  11. data/lib/avr/instruction.rb +72 -0
  12. data/lib/avr/memory.rb +163 -0
  13. data/lib/avr/memory/eeprom.rb +65 -0
  14. data/lib/avr/memory/flash.rb +13 -0
  15. data/lib/avr/memory/memory_byte.rb +54 -0
  16. data/lib/avr/memory/sram.rb +13 -0
  17. data/lib/avr/opcode.rb +237 -0
  18. data/lib/avr/opcode/branch/conditional.rb +61 -0
  19. data/lib/avr/opcode/branch/return.rb +23 -0
  20. data/lib/avr/opcode/branch/unconditional.rb +74 -0
  21. data/lib/avr/opcode/break.rb +14 -0
  22. data/lib/avr/opcode/compare.rb +66 -0
  23. data/lib/avr/opcode/data/immediate.rb +14 -0
  24. data/lib/avr/opcode/data/program.rb +25 -0
  25. data/lib/avr/opcode/data/sram.rb +222 -0
  26. data/lib/avr/opcode/data/stack.rb +22 -0
  27. data/lib/avr/opcode/io/bit.rb +22 -0
  28. data/lib/avr/opcode/io/in_out.rb +38 -0
  29. data/lib/avr/opcode/math/addition.rb +120 -0
  30. data/lib/avr/opcode/math/bitwise.rb +170 -0
  31. data/lib/avr/opcode/math/multiplication.rb +23 -0
  32. data/lib/avr/opcode/math/subtraction.rb +137 -0
  33. data/lib/avr/opcode/nop.rb +14 -0
  34. data/lib/avr/opcode/opcodes.rb +24 -0
  35. data/lib/avr/opcode/operand_parsers.rb +75 -0
  36. data/lib/avr/opcode/register.rb +37 -0
  37. data/lib/avr/opcode/sleep.rb +14 -0
  38. data/lib/avr/opcode/sreg.rb +56 -0
  39. data/lib/avr/opcode/wdr.rb +14 -0
  40. data/lib/avr/opcode_decoder.rb +202 -0
  41. data/lib/avr/oscillator.rb +33 -0
  42. data/lib/avr/port.rb +107 -0
  43. data/lib/avr/register.rb +18 -0
  44. data/lib/avr/register/memory_byte_register.rb +27 -0
  45. data/lib/avr/register/memory_byte_register_with_named_bits.rb +85 -0
  46. data/lib/avr/register/register_file.rb +59 -0
  47. data/lib/avr/register/register_pair.rb +46 -0
  48. data/lib/avr/register/sp.rb +41 -0
  49. data/lib/avr/register/sreg.rb +12 -0
  50. data/lib/avr/register_with_bit_number.rb +40 -0
  51. data/lib/avr/register_with_displacement.rb +31 -0
  52. data/lib/avr/register_with_modification.rb +35 -0
  53. data/lib/avr/register_with_named_bit.rb +36 -0
  54. data/lib/avr/value.rb +45 -0
  55. data/lib/avr/version.rb +6 -0
  56. metadata +182 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 63999499c4aa4ad74832d1336a0831f70cb6d27db1c56289ce34990bc4067a88
4
+ data.tar.gz: ee091a1f40ced91acd72fb586a5dddb607df0c149c120c39ae94fee704033302
5
+ SHA512:
6
+ metadata.gz: 6b9ef4be22b80c0360c4f0c171cf84c7bf988625598187955945f96b71ae3e708d2f9e23deadd6cfa543300e70bb9bb4d96b526335b4ab4a2e42ff561fb5138a
7
+ data.tar.gz: 0e4229b9af1f2c876db49d9f23b1133d08cf4270f1affa9115cfec5ba8a2a54b41ad58a2a3865792ebbd30954b087b66731e6526bb082492f4992a097f6fa57f
@@ -0,0 +1,28 @@
1
+ This software is licensed under the Revised (3-clause) BSD license as follows:
2
+
3
+ Copyright (c) 2019, Jeremy Cole <jeremy@jcole.us>
4
+
5
+ All rights reserved.
6
+
7
+ Redistribution and use in source and binary forms, with or without
8
+ modification, are permitted provided that the following conditions are met:
9
+
10
+ * Redistributions of source code must retain the above copyright
11
+ notice, this list of conditions and the following disclaimer.
12
+ * Redistributions in binary form must reproduce the above copyright
13
+ notice, this list of conditions and the following disclaimer in the
14
+ documentation and/or other materials provided with the distribution.
15
+ * Neither the name of the <organization> nor the
16
+ names of its contributors may be used to endorse or promote products
17
+ derived from this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
23
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,17 @@
1
+ # AVRuby
2
+
3
+ [![Build Status](https://travis-ci.org/jeremycole/avruby.svg?branch=master)](https://travis-ci.org/jeremycole/avruby)
4
+
5
+ ## tl;dr
6
+
7
+ This is an AVR emulator, written really stupidly in Ruby. It's not supposed to be practical, it's supposed to be educational, and maybe a bit of fun.
8
+
9
+ ## Status
10
+
11
+ I initially started this as an exercise in parsing some opcodes, as I was learning how the AVR instruction set worked while learning AVR assembly. Once I could parse a few things, I thought "Hey, I could execute these in Ruby – haha!" and AVRuby was born.
12
+
13
+ The CPU is nearly fully implemented. SRAM, Flash, and EEPROM memories are supported. Most opcodes work. Support for loading the flash (or any memory) from Intel hex is supported, so it's easy to load existing code. Basic RSpec tests are implemented for opcodes and some other things. Most peripherals are not implemented. Contributions (or insults) are welcome.
14
+
15
+ Currently, many simple programs (such as blink) will run out of the box, although the CPU as emulated is exceedingly slow. It runs at ~140 kHz on a MacBook Pro with 2.8 GHz Intel Core i7 using Ruby 2.6.3.
16
+
17
+ # It's fun.
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env ruby
2
+ # typed: strict
3
+ # frozen_string_literal: true
4
+
5
+ require 'optparse'
6
+ require 'avr'
7
+ require 'irb'
8
+ require 'irb/completion'
9
+ require 'pp'
10
+
11
+ def initialize_device
12
+ print 'Initializing device... '
13
+ @device = AVR::Device::Atmel_ATmega328p.new
14
+ @cpu = @device.cpu
15
+ puts 'OK.'
16
+ end
17
+
18
+ def load_intel_hex_files(files)
19
+ puts 'Loading Intel hex firmware into Flash...'
20
+ files.each do |hex_file|
21
+ print "- Loading #{hex_file}... "
22
+ bytes_loaded = @device.flash.load_from_intel_hex(hex_file)
23
+ puts "OK, loaded #{bytes_loaded} bytes."
24
+ end
25
+ end
26
+
27
+ def enable_tracing
28
+ print 'Setting up tracing... '
29
+ @device.trace_all
30
+ puts 'OK.'
31
+ end
32
+
33
+ def shell
34
+ puts 'Typically, you should:'
35
+ puts ' Use @device to access the entire device.'
36
+ puts ' Use @cpu (or @device.cpu) to access the CPU.'
37
+ puts ' Some useful things to run:'
38
+ puts ' @device.oscillator.tick to single step.'
39
+ puts ' @device.oscillator.run(5.times) to run for 5 ticks.'
40
+ puts ' @device.oscillator.run to run indefinitely.'
41
+ puts 'Ready. Have fun!'
42
+ puts
43
+
44
+ irb = IRB::Irb.new
45
+ irb.run(IRB.conf)
46
+
47
+ puts 'Stopped.'
48
+ puts
49
+ end
50
+
51
+ def benchmark_warmup
52
+ print 'Warming up for %0.1f seconds... ' % [@options[:benchmark_warmup_time]]
53
+ warmup_ticks = @device.oscillator.run_timed(@options[:benchmark_warmup_time])
54
+ puts 'OK.'
55
+
56
+ puts 'Execution speed during warmup: %.2f kHz' % [
57
+ warmup_ticks.to_f / @options[:benchmark_warmup_time] / 1000.0,
58
+ ]
59
+ puts
60
+ end
61
+
62
+ def benchmark_time
63
+ print 'Benchmarking for %0.1f seconds... ' % [@options[:benchmark_time]]
64
+ benchmark_ticks = @device.oscillator.run_timed(@options[:benchmark_time])
65
+ puts 'OK.'
66
+
67
+ puts 'Execution speed: %.2f kHz over %0.2f seconds' % [
68
+ benchmark_ticks.to_f / @options[:benchmark_time] / 1000.0,
69
+ @options[:benchmark_time],
70
+ ]
71
+ puts
72
+ end
73
+
74
+ def benchmark_ticks
75
+ print 'Benchmarking for %d ticks... ' % [@options[:benchmark_ticks]]
76
+ start_time = Time.now.to_f
77
+ @device.oscillator.run(@options[:benchmark_ticks].times)
78
+ end_time = Time.now.to_f
79
+ puts 'OK.'
80
+
81
+ benchmark_time = end_time - start_time
82
+
83
+ puts 'Execution speed: %.2f kHz over %0.2f seconds' % [
84
+ @options[:benchmark_ticks].to_f / benchmark_time / 1000.0,
85
+ benchmark_time,
86
+ ]
87
+ puts
88
+ end
89
+
90
+ def benchmark
91
+ benchmark_warmup if @options[:benchmark_warmup_time]
92
+ benchmark_time if @options[:benchmark_time]
93
+ benchmark_ticks if @options[:benchmark_ticks]
94
+ end
95
+
96
+ def print_opcode_decoder_cache
97
+ @device.cpu.decoder.print_cache
98
+ puts
99
+ end
100
+
101
+ IRB.setup(nil, argv: ['-f'])
102
+
103
+ IRB.conf[:IRB_NAME] = 'avr'
104
+ IRB.conf[:SAVE_HISTORY] = 1000
105
+ IRB.conf[:HISTORY_FILE] = File.join(ENV['HOME'], '.avruby_shell_history')
106
+ IRB.conf[:PROMPT][:avruby_shell] = {
107
+ PROMPT_I: "\u001b[0;34;1m%N>\u001b[0m ",
108
+ PROMPT_N: "\u001b[0;34;1m%N>\u001b[0m ",
109
+ PROMPT_S: "\u001b[0;33;1m%N+\u001b[0m ",
110
+ PROMPT_C: "\u001b[0;35;1m%N*\u001b[0m ",
111
+ RETURN: "\u001b[0;31;1m=>\u001b[0m \u001b[32m%s\u001b[0m\n\n",
112
+ }
113
+ IRB.conf[:PROMPT_MODE] = :avruby_shell
114
+
115
+ @options = {
116
+ trace: false,
117
+ benchmark_time: nil,
118
+ benchmark_ticks: nil,
119
+ benchmark_warmup_time: nil,
120
+ benchmark_print_opcode_cache: false,
121
+ intel_hex_files: [],
122
+ }
123
+
124
+ OptionParser.new do |opts|
125
+ opts.on('-h', '--help', 'Show this help.') do
126
+ puts opts
127
+ puts
128
+ exit
129
+ end
130
+ opts.on('-b', '--benchmark-time=TIME', 'Specify the run time, in seconds.') do |o|
131
+ @options[:benchmark_time] = o.to_f
132
+ end
133
+ opts.on('-B', '--benchmark-ticks=TICKS', 'Specify the run time, in ticks.') do |o|
134
+ @options[:benchmark_ticks] = o.to_i
135
+ end
136
+ opts.on('-w', '--benchmark-warmup-time=TIME', 'Specify the warmup time, in seconds.') do |o|
137
+ @options[:benchmark_warmup_time] = o.to_f
138
+ end
139
+ opts.on('-d', '--[no-]benchmark-print-opcode-cache', 'Enable print of opcode decoder cache after benchmark.') do |o|
140
+ @options[:benchmark_print_opcode_cache] = o
141
+ end
142
+ opts.on('-t', '--[no-]trace', 'Enable tracing.') do |o|
143
+ @options[:trace] = o
144
+ end
145
+ opts.on('-I', '--intel-hex-file=FILE', 'Load Intel hex file.') do |o|
146
+ @options[:intel_hex_files] << o
147
+ end
148
+ end.parse!
149
+
150
+ initialize_device
151
+ load_intel_hex_files(@options[:intel_hex_files]) unless @options[:intel_hex_files].empty?
152
+ enable_tracing if @options[:trace]
153
+
154
+ puts
155
+
156
+ if @options[:benchmark_time] || @options[:benchmark_ticks]
157
+ benchmark
158
+ else
159
+ shell
160
+ end
161
+
162
+ print_opcode_decoder_cache if @options[:benchmark_print_opcode_cache]
@@ -0,0 +1,41 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+ if ENV.include?('SORBET_DEFAULT_CHECKED_LEVEL')
6
+ level = ENV.fetch('SORBET_DEFAULT_CHECKED_LEVEL').to_sym
7
+ puts "Setting Sorbet default_checked_level to :#{level}!"
8
+ puts
9
+
10
+ T::Configuration.default_checked_level = level
11
+ end
12
+
13
+ require 'avr/version'
14
+ require 'avr/value'
15
+ require 'avr/memory'
16
+ require 'avr/memory/sram'
17
+ require 'avr/memory/eeprom'
18
+ require 'avr/memory/flash'
19
+ require 'avr/register'
20
+ require 'avr/register_with_bit_number'
21
+ require 'avr/register_with_displacement'
22
+ require 'avr/register_with_modification'
23
+ require 'avr/register/memory_byte_register'
24
+ require 'avr/register/memory_byte_register_with_named_bits'
25
+ require 'avr/register_with_named_bit'
26
+ require 'avr/register/register_pair'
27
+ require 'avr/register/register_file'
28
+ require 'avr/register/sreg'
29
+ require 'avr/register/sp'
30
+ require 'avr/argument'
31
+ require 'avr/cpu'
32
+ require 'avr/opcode_decoder'
33
+ require 'avr/opcode'
34
+ require 'avr/instruction'
35
+ require 'avr/opcode/opcodes'
36
+ require 'avr/clock'
37
+ require 'avr/oscillator'
38
+ require 'avr/device'
39
+ require 'avr/port'
40
+
41
+ require 'avr/device/atmel_atmega328p'
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module AVR
5
+ module Argument
6
+ extend T::Sig
7
+
8
+ ValueType = T.type_alias do
9
+ T.any(
10
+ Value,
11
+ Register,
12
+ RegisterPair,
13
+ MemoryByteRegister,
14
+ RegisterWithDisplacement,
15
+ RegisterWithModification,
16
+ RegisterWithBitNumber,
17
+ RegisterWithNamedBit
18
+ )
19
+ end
20
+ ArrayType = T.type_alias { T::Array[ValueType] }
21
+ NamedValueType = T.type_alias { T::Hash[Symbol, ValueType] }
22
+ end
23
+ end
@@ -0,0 +1,99 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module AVR
5
+ class Clock
6
+ extend T::Sig
7
+
8
+ class Sink
9
+ extend T::Sig
10
+
11
+ sig do
12
+ params(
13
+ name: T.nilable(String),
14
+ sink_proc: T.nilable(T.proc.params(source: Clock, ticks: Integer).void),
15
+ block: T.nilable(T.proc.params(source: Clock, ticks: Integer).void)
16
+ ).void
17
+ end
18
+ def initialize(name = nil, sink_proc = nil, &block)
19
+ raise unless sink_proc || block_given?
20
+ @name = name
21
+ @sink_proc = T.let(
22
+ sink_proc || block&.to_proc,
23
+ T.nilable(T.proc.params(source: Clock, ticks: Integer).void)
24
+ )
25
+ end
26
+
27
+ sig { params(source: Clock, ticks: Integer).void }
28
+ def tick(source, ticks)
29
+ T.must(@sink_proc).call(source, ticks)
30
+ end
31
+ end
32
+
33
+ sig { returns(T.nilable(String)) }
34
+ attr_reader :name
35
+
36
+ sig { returns(T::Array[T.any(Clock, Sink)]) }
37
+ attr_reader :sinks
38
+
39
+ sig { returns(T::Hash[Integer, T.untyped]) }
40
+ attr_reader :watches
41
+
42
+ sig { returns (Integer) }
43
+ attr_accessor :count
44
+
45
+ sig { returns (Integer) }
46
+ attr_accessor :ticks
47
+
48
+ sig { returns (Integer) }
49
+ attr_accessor :scale
50
+
51
+ sig { params(name: T.nilable(String)).void }
52
+ def initialize(name = nil)
53
+ @name = name
54
+ @sinks = T.let([], T::Array[Sink])
55
+ @watches = T.let({}, T::Hash[Integer, T.untyped])
56
+ @ticks = T.let(0, Integer)
57
+ @count = T.let(0, Integer)
58
+ @scale = T.let(1, Integer)
59
+ @last_tick = T.let(0, Integer)
60
+ end
61
+
62
+ sig { params(sink: Sink).void }
63
+ def unshift_sink(sink)
64
+ sinks.unshift(sink)
65
+ end
66
+
67
+ sig { params(sink: T.any(Clock, Sink)).void }
68
+ def push_sink(sink)
69
+ sinks.push(sink)
70
+ end
71
+
72
+ sig { void }
73
+ def clear_sinks
74
+ @sinks = []
75
+ end
76
+
77
+ sig { params(tick: Integer, sink: Sink).void }
78
+ def notify_at_tick(tick, sink)
79
+ @watches[tick] ||= []
80
+ @watches[tick] << sink
81
+ end
82
+
83
+ sig { params(_source: T.nilable(Clock), _ticks: T.nilable(Integer)).returns(Integer) }
84
+ def tick(_source = nil, _ticks = nil)
85
+ @count += 1
86
+ if (@count % @scale).zero?
87
+ @last_tick = @ticks
88
+ sinks.each do |sink|
89
+ sink.tick(self, @ticks)
90
+ end
91
+ watches[@ticks]&.each do |watch|
92
+ watch.tick(self, @ticks)
93
+ end
94
+ @ticks += 1
95
+ end
96
+ @last_tick
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,254 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module AVR
5
+ class CPU
6
+ extend T::Sig
7
+
8
+ sig { returns(Device) }
9
+ attr_reader :device
10
+
11
+ sig { returns(Integer) }
12
+ attr_accessor :pc
13
+
14
+ sig { returns(Integer) }
15
+ attr_accessor :next_pc
16
+
17
+ sig { returns(Memory) }
18
+ attr_reader :sram
19
+
20
+ sig { returns(RegisterFile) }
21
+ attr_reader :registers
22
+
23
+ sig { returns(Register) }
24
+ def r0
25
+ registers.fetch(:r0)
26
+ end
27
+
28
+ sig { params(value: Integer).void }
29
+ def r0=(value)
30
+ registers.fetch(:r0).value = value
31
+ end
32
+
33
+ sig { returns(Register) }
34
+ def r1
35
+ registers.fetch(:r1)
36
+ end
37
+
38
+ sig { params(value: Integer).void }
39
+ def r1=(value)
40
+ registers.fetch(:r1).value = value
41
+ end
42
+
43
+ sig { returns(Register) }
44
+ def X
45
+ registers.fetch(:X)
46
+ end
47
+
48
+ sig { returns(Register) }
49
+ def Y
50
+ registers.fetch(:Y)
51
+ end
52
+
53
+ sig { returns(Register) }
54
+ def Z
55
+ registers.fetch(:Z)
56
+ end
57
+
58
+ sig { returns(RegisterFile) }
59
+ attr_reader :io_registers
60
+
61
+ # SREG uses metaprogramming that Sorbet doesn't understand
62
+ # sig { returns(SREG) }
63
+ sig { returns(T.untyped) }
64
+ attr_reader :sreg
65
+
66
+ sig { returns(SP) }
67
+ attr_reader :sp
68
+
69
+ sig { returns(OpcodeDecoder) }
70
+ attr_reader :decoder
71
+
72
+ sig { returns(T::Hash[Symbol, Port]) }
73
+ attr_reader :ports
74
+
75
+ sig { returns(Clock) }
76
+ attr_reader :clock
77
+
78
+ sig { returns(T.nilable(T.proc.params(arg0: Instruction).void)) }
79
+ attr_reader :tracer
80
+
81
+ sig { params(device: Device).void }
82
+ def initialize(device)
83
+ @device = device
84
+ @pc = 0
85
+ @next_pc = 0
86
+ @sram = SRAM.new(device.ram_start + device.sram_size)
87
+
88
+ @registers = RegisterFile.new(self)
89
+
90
+ device.register_count.times do |n|
91
+ registers.add(MemoryByteRegister.new(self, "r#{n}", @sram.memory[n]))
92
+ end
93
+
94
+ device.word_register_map.each do |name, map|
95
+ registers.add(RegisterPair.new(self, @registers[map[:l]], @registers[map[:h]], name))
96
+ end
97
+
98
+ @io_registers = RegisterFile.new(self)
99
+ device.io_registers.each do |name|
100
+ address = device.data_memory_map[name]
101
+ next unless address
102
+
103
+ bit_names = device.register_bit_names_map[name]
104
+ if bit_names
105
+ io_registers.add(MemoryByteRegisterWithNamedBits.new(self, name.to_s, @sram.memory[address], bit_names))
106
+ else
107
+ io_registers.add(MemoryByteRegister.new(self, name.to_s, @sram.memory[address]))
108
+ end
109
+ end
110
+
111
+ @sreg = SREG.new(self)
112
+
113
+ @sp = SP.new(
114
+ self,
115
+ @sram.memory[device.data_memory_map[:SPL]],
116
+ @sram.memory[device.data_memory_map[:SPH]],
117
+ device.ram_end
118
+ )
119
+
120
+ @decoder = OpcodeDecoder.new
121
+
122
+ @ports = {}
123
+ device.port_map.each do |name, addr|
124
+ @ports[name] = Port.new(self, name, addr[:pin], addr[:ddr], addr[:port])
125
+ end
126
+
127
+ @clock = Clock.new('cpu')
128
+ @clock.push_sink(Clock::Sink.new('cpu') { step })
129
+
130
+ @tracer = nil
131
+ end
132
+
133
+ def notify_at_tick(tick, &block)
134
+ clock.notify_at_tick(tick, Clock::Sink.new("notify #{block} at #{tick}", block.to_proc))
135
+ end
136
+
137
+ sig { params(block: T.nilable(T.proc.params(arg0: Instruction).void)).void }
138
+ def trace(&block)
139
+ @tracer = nil
140
+ @tracer = block.to_proc if block_given?
141
+ end
142
+
143
+ sig { void }
144
+ def print_status
145
+ puts 'Status:'
146
+ puts '%8s = %d' % ['Ticks', clock.ticks]
147
+ puts '%8s = %d opcodes' % ['Cache', decoder.cache.size]
148
+ puts '%8s = 0x%04x words' % ['PC', pc]
149
+ puts '%8s = 0x%04x bytes' % ['PC', pc * 2]
150
+ puts '%8s = 0x%04x (%d bytes used)' % ['SP', sp.value, device.ram_end - sp.value]
151
+ puts '%8s = 0x%02x [%s]' % ['SREG', sreg.value, sreg.bit_values]
152
+ puts
153
+ puts 'Registers:'
154
+ registers.print_status
155
+ puts
156
+ puts 'IO Registers:'
157
+ io_registers.print_status
158
+ puts
159
+ puts 'IO Ports:'
160
+ puts '%4s %s' % ['', Port::PINS.join(' ')]
161
+ ports.each do |name, port|
162
+ puts '%4s: %s' % [name, port.pin_states.join(' ')]
163
+ end
164
+ puts
165
+ puts 'Next instruction:'
166
+ puts ' ' + peek.to_s
167
+ puts
168
+ end
169
+
170
+ sig { void }
171
+ def reset_to_clean_state
172
+ reset
173
+ registers.reset
174
+ io_registers.reset
175
+ sram.reset
176
+ sp.value = device.ram_end
177
+ end
178
+
179
+ sig { void }
180
+ def reset
181
+ @pc = 0
182
+ @next_pc = 0
183
+ sreg.reset
184
+ end
185
+
186
+ sig { returns(Integer) }
187
+ def fetch
188
+ word = device.flash.word(next_pc)
189
+ @next_pc += 1
190
+ word
191
+ end
192
+
193
+ sig { params(mnemonic: Symbol, args: T.untyped).returns(Instruction) }
194
+ def instruction(mnemonic, *args)
195
+ Instruction.new(self, mnemonic, args)
196
+ end
197
+
198
+ sig { params(name_or_vector_number: T.any(Symbol, Integer)).void }
199
+ def interrupt(name_or_vector_number)
200
+ sreg.I = false
201
+ case name_or_vector_number
202
+ when Integer
203
+ address = name_or_vector_number * 2
204
+ when Symbol
205
+ address = device.interrupt_vector_map[name_or_vector_number]
206
+ end
207
+
208
+ instruction(:call, Value.new(address)).execute
209
+ end
210
+
211
+ sig { returns(Instruction) }
212
+ def decode
213
+ offset = next_pc
214
+ word = fetch
215
+ decoded_opcode = decoder.decode(word)
216
+ unless decoded_opcode
217
+ raise 'Unable to decode 0x%04x at offset 0x%04x words (0x%04x bytes)' % [
218
+ word,
219
+ offset,
220
+ offset * 2,
221
+ ]
222
+ end
223
+
224
+ decoded_opcode.opcode_definition.parse(
225
+ self,
226
+ decoded_opcode.opcode_definition,
227
+ decoded_opcode.prepare_operands(self)
228
+ )
229
+ end
230
+
231
+ sig { returns(Instruction) }
232
+ def peek
233
+ save_pc = pc
234
+ save_next_pc = next_pc
235
+ i = decode
236
+ @pc = save_pc
237
+ @next_pc = save_next_pc
238
+ i
239
+ end
240
+
241
+ sig { void }
242
+ def step
243
+ i = decode
244
+ @tracer&.call(i)
245
+ begin
246
+ i.execute
247
+ rescue StandardError
248
+ puts "*** Caught exception while executing #{i}, CPU status:"
249
+ print_status
250
+ raise
251
+ end
252
+ end
253
+ end
254
+ end