Class: LC3

Inherits:
Object
  • Object
show all
Includes:
LC3Spec::Helpers
Defined in:
lib/lc3spec/lc3.rb

Overview

Class to provide access to LC-3 simulator instance

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from LC3Spec::Helpers

#is_absolute_path?, #normalize_to_i, #normalize_to_s

Constructor Details

#initializeLC3

Returns a new instance of LC3.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/lc3spec/lc3.rb', line 17

def initialize
  # Logging
  @logger = Logger.new(STDERR)
  if ENV['LC3DEBUG']
    @logger.level = Logger::DEBUG
  else
    @logger.level = Logger::ERROR
  end
  @logger.formatter = proc do |severity, datetime, progname, msg|
    "#{severity}: #{msg}\n"
  end

  # Registers
  @registers = {}
  [:R0, :R1, :R2, :R3, :R4, :R5, :R6, :R7, :PC, :IR, :PSR].each do |reg|
    @registers[reg] = 'x0000'
  end
  @registers[:CC] = 'ZERO'

  # Memory
  @memory = Hash.new('x0000')
  @labels = {}

  initialize_lc3sim
end

Instance Attribute Details

#loggerObject

Returns the value of attribute logger.



15
16
17
# File 'lib/lc3spec/lc3.rb', line 15

def logger
  @logger
end

Instance Method Details

#clear_breakpoint(addr) ⇒ self

Clear a breakpoint at an address or label

Parameters:

  • addr (String)

    an address in hex format (e.g., ‘xADD4’) or a label

Returns:

  • (self)

Raises:

  • (ArgumentError)

    if the argument is not a existing label or is an invalid address



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/lc3spec/lc3.rb', line 233

def clear_breakpoint(addr)
  if addr == :all
    @io.puts 'break clear all'
    return self
  end

  addr = addr.upcase.to_s if addr.respond_to? :upcase

  if @labels.include? addr
    @io.puts "break clear #{addr}"
  else
    @io.puts "break clear #{normalize_to_s(addr)}"
  end

  sleep(0.01)

  while @io.ready?
    msg = @io.readline
    parse_msg msg.strip
  end

  self
end

#clear_breakpointsself

Clear all breakpoints

Returns:

  • (self)


260
261
262
# File 'lib/lc3spec/lc3.rb', line 260

def clear_breakpoints
  clear_breakpoint :all
end

#closenil

Close open file descriptors for communicating with lc3sim

Returns:

  • (nil)


305
306
307
308
309
# File 'lib/lc3spec/lc3.rb', line 305

def close
  @io.close
  @output.close
  @server.close
end

#continueself

Execute instructions until halt or breakpoint

Returns:

  • (self)


197
198
199
200
201
202
203
# File 'lib/lc3spec/lc3.rb', line 197

def continue
  @io.puts 'continue'

  parse_until_print_registers

  self
end

#file(filename) ⇒ self

Load a file given a path

Parameters:

  • filename (String)

    the path of a file to load into lc3sim. The argument should have .obj extension or no extension at all, in which case .obj is assumed

Returns:

  • (self)


158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/lc3spec/lc3.rb', line 158

def file(filename)
  @io.puts "file #{filename}"

  # Need to encounter 2 TOCODEs before we're done
  tocode_counter = 2

  while tocode_counter > 0
    msg = @io.readline

    parse_msg msg.strip

    if msg =~ /^TOCODE/
      tocode_counter -= 1
    end

    # ignore warning about no symbols
    next if msg =~ /WARNING: No symbols/

    # don't ignore other errors
    break if msg =~ /^ERR/
  end

  self
end

#get_address(label) ⇒ String?

Get the address of a label

Parameters:

  • label (String)

Returns:

  • (String, nil)

    the address of the label, or nil if the label does not exist



148
149
150
# File 'lib/lc3spec/lc3.rb', line 148

def get_address(label)
  @labels[label.upcase.to_s]
end

#get_memory(addr) ⇒ String

Return value in memory at the given address or label

Parameters:

  • addr (String)

    an address in hex format (e.g., ‘xADD4’) or a label

Returns:

  • (String)

    the value in memory in hex format

Raises:

  • (ArgumentError)

    if the argument is not a existing label or is an invalid address



93
94
95
96
97
98
99
100
101
# File 'lib/lc3spec/lc3.rb', line 93

def get_memory(addr)
  if addr.respond_to?(:upcase)
    label_addr = get_address(addr.upcase)

    return @memory[label_addr] unless label_addr.nil?
  end

  @memory[normalize_to_s(addr)]
end

#get_outputString

Return output from the LC-3

The LC-3 welcome messages and halt messages are stripped from the output, so that it can easily compared to expected output.

WARNING: This is potentially buggy as there is no signal from lc3sim when the output is ready, so this function just waits until output appears and reads what is available. This may not work if the output is very long.

Returns:

  • (String)

    the output from the LC-3, or an empty string if there is no output



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/lc3spec/lc3.rb', line 275

def get_output
  out = ''

  # Do something to wait for output to be ready
  set_register(:PSR, @registers[:PSR])

  # There is no signal that tells the GUI that output is ready...
  # FIXME: This is a bug waiting to happen
  catch(:done) do
    loop do
      retries = 5
      until @output.ready?
        sleep(0.1)

        retries -= 1
        throw :done if retries <= 0
      end

      while @output.ready?
        out << @output.readpartial(1024)
      end
    end
  end

  out.gsub("\n\n--- halting the LC-3 ---\n\n", '')
end

#get_register(reg) ⇒ String?

Get the value of a register

Parameters:

  • reg (Symbol)

    the register, one of :R0 through :R7, :PC, :IR, :PSR, or :CC

Returns:

  • (String, nil)

    the register value in hex format (e.g., ‘x0000’) or nil if the register does not exist.



49
50
51
52
# File 'lib/lc3spec/lc3.rb', line 49

def get_register(reg)
  reg = reg.to_s.upcase.to_sym  # Ruby 1.8 doesn't support Symbol#upcase
  @registers[reg]
end

#inspectString

Return a string containing a human-readable representation of the current

state of the LC-3

Returns:

  • (String)


315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/lc3spec/lc3.rb', line 315

def inspect
  registers = @registers.map { |k, v| "#{k}=#{v}" }.join(' ')

  addr_to_label = @labels.invert

  memory_header = "%18s ADDR  VALUE" % "label"
  memory = @memory.map do |addr, value|
    "%18s %s %s" % [(addr_to_label[addr] or ''), addr, value]
  end.join("\n")

  #[memory_header, memory, registers].join("\n")
  registers
end

#set_breakpoint(addr) ⇒ self

Set a breakpoint at an address or label

Parameters:

  • addr (String)

    an address in hex format (e.g., ‘xADD4’) or a label

Returns:

  • (self)

Raises:

  • (ArgumentError)

    if the argument is not a existing label or is an invalid address



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/lc3spec/lc3.rb', line 210

def set_breakpoint(addr)
  addr = addr.upcase.to_s if addr.respond_to? :upcase
  if @labels.include? addr
    @io.puts "break set #{addr}"
  else
    @io.puts "break set #{normalize_to_s(addr)}"
  end

  sleep(0.01)

  while @io.ready?
    msg = @io.readline
    parse_msg msg.strip
  end

  self
end

#set_memory(addr, val) ⇒ self

Set memory at the given address or label

If val is a label, mem will be set to the address of val

Parameters:

  • addr (String)

    an address or a label

  • val (String)

    a value or a label

Returns:

  • (self)

Raises:

  • (ArgumentError)

    if addr is not an existing label, addr is an invalid address, val is not an existing label, or val is invalid



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/lc3spec/lc3.rb', line 112

def set_memory(addr, val)
  # addr may be memory address or label

  if addr.respond_to?(:upcase) and @labels.include?(addr.upcase.to_s)
    # Is a label
    addr = addr.upcase.to_s
  else
    # Not a label
    addr = normalize_to_s(addr)
  end

  # Value can be a label too
  if val.respond_to?(:upcase) and @labels.include?(val.upcase.to_s)
    # Is a label
  else
    # Is a value
    val = normalize_to_s(val)
  end

  @io.puts("memory #{addr} #{val}")

  loop do
    msg = @io.readline
    parse_msg msg.strip

    break if msg =~ /^ERR|CODE/
  end

  self
end

#set_register(reg, val) ⇒ self

Set the value of a register

Parameters:

  • val (String)

    the register value in hex format (e.g., ‘xF1D0’)

  • reg (Symbol)

    the register, one of :R0 through :R7, :PC, :IR, :PSR, or :CC

Returns:

  • (self)

Raises:

  • (ArgumentError)

    if the register is invalid or the value is nil,



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/lc3spec/lc3.rb', line 60

def set_register(reg, val)
  reg = reg.upcase

  unless @registers.keys.include? reg.to_sym
    raise ArgumentError, "Invalid register: #{reg.to_s}"
  end

  if val.nil?
    raise ArgumentError, "Invalid register value for #{reg.to_s}: #{val}"
  end

  if reg.to_sym == :CC and not ['POSITIVE', 'NEGATIVE', 'ZERO'].include? val
    raise ArgumentError, "CC can only be set to NEGATIVE, ZERO, or POSITIVE"
  end

  @io.puts "register #{reg.to_s} #{normalize_to_s(val)}"

  loop do
    msg = @io.readline
    parse_msg msg.strip

    break if msg =~ /^ERR|TOCODE/
  end

  self
end

#stepself

Execute one instruction

Returns:

  • (self)


186
187
188
189
190
191
192
# File 'lib/lc3spec/lc3.rb', line 186

def step
  @io.puts 'step'

  parse_until_print_registers

  self
end