avruby 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.md +28 -0
- data/README.md +17 -0
- data/bin/avruby_shell +162 -0
- data/lib/avr.rb +41 -0
- data/lib/avr/argument.rb +23 -0
- data/lib/avr/clock.rb +99 -0
- data/lib/avr/cpu.rb +254 -0
- data/lib/avr/device.rb +249 -0
- data/lib/avr/device/atmel_atmega328p.rb +181 -0
- data/lib/avr/instruction.rb +72 -0
- data/lib/avr/memory.rb +163 -0
- data/lib/avr/memory/eeprom.rb +65 -0
- data/lib/avr/memory/flash.rb +13 -0
- data/lib/avr/memory/memory_byte.rb +54 -0
- data/lib/avr/memory/sram.rb +13 -0
- data/lib/avr/opcode.rb +237 -0
- data/lib/avr/opcode/branch/conditional.rb +61 -0
- data/lib/avr/opcode/branch/return.rb +23 -0
- data/lib/avr/opcode/branch/unconditional.rb +74 -0
- data/lib/avr/opcode/break.rb +14 -0
- data/lib/avr/opcode/compare.rb +66 -0
- data/lib/avr/opcode/data/immediate.rb +14 -0
- data/lib/avr/opcode/data/program.rb +25 -0
- data/lib/avr/opcode/data/sram.rb +222 -0
- data/lib/avr/opcode/data/stack.rb +22 -0
- data/lib/avr/opcode/io/bit.rb +22 -0
- data/lib/avr/opcode/io/in_out.rb +38 -0
- data/lib/avr/opcode/math/addition.rb +120 -0
- data/lib/avr/opcode/math/bitwise.rb +170 -0
- data/lib/avr/opcode/math/multiplication.rb +23 -0
- data/lib/avr/opcode/math/subtraction.rb +137 -0
- data/lib/avr/opcode/nop.rb +14 -0
- data/lib/avr/opcode/opcodes.rb +24 -0
- data/lib/avr/opcode/operand_parsers.rb +75 -0
- data/lib/avr/opcode/register.rb +37 -0
- data/lib/avr/opcode/sleep.rb +14 -0
- data/lib/avr/opcode/sreg.rb +56 -0
- data/lib/avr/opcode/wdr.rb +14 -0
- data/lib/avr/opcode_decoder.rb +202 -0
- data/lib/avr/oscillator.rb +33 -0
- data/lib/avr/port.rb +107 -0
- data/lib/avr/register.rb +18 -0
- data/lib/avr/register/memory_byte_register.rb +27 -0
- data/lib/avr/register/memory_byte_register_with_named_bits.rb +85 -0
- data/lib/avr/register/register_file.rb +59 -0
- data/lib/avr/register/register_pair.rb +46 -0
- data/lib/avr/register/sp.rb +41 -0
- data/lib/avr/register/sreg.rb +12 -0
- data/lib/avr/register_with_bit_number.rb +40 -0
- data/lib/avr/register_with_displacement.rb +31 -0
- data/lib/avr/register_with_modification.rb +35 -0
- data/lib/avr/register_with_named_bit.rb +36 -0
- data/lib/avr/value.rb +45 -0
- data/lib/avr/version.rb +6 -0
- metadata +182 -0
checksums.yaml
ADDED
@@ -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
|
data/LICENSE.md
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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.
|
data/bin/avruby_shell
ADDED
@@ -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]
|
data/lib/avr.rb
ADDED
@@ -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'
|
data/lib/avr/argument.rb
ADDED
@@ -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
|
data/lib/avr/clock.rb
ADDED
@@ -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
|
data/lib/avr/cpu.rb
ADDED
@@ -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
|