zemu 0.4.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/zemu/config.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'ostruct'
2
+
1
3
  module Zemu
2
4
  # Abstract configuration object.
3
5
  # All configuration objects should inherit from this.
@@ -75,10 +77,123 @@ module Zemu
75
77
  # @param [String] compiler The path to the compiler to be used for compiling the emulator executable.
76
78
  #
77
79
  class Config < ConfigObject
80
+ # Bus Device.
81
+ #
82
+ # Represents a device connected to the I/O
83
+ # or memory buses, or both.
84
+ class BusDevice < ConfigObject
85
+ # Constructor.
86
+ #
87
+ # This object should not be constructed directly.
88
+ def initialize
89
+ if self.class == Zemu::Config::BusDevice
90
+ raise NotImplementedError, "Cannot construct an instance of the abstract class Zemu::Config::BusDevice."
91
+ end
92
+
93
+ @nmi = false
94
+ @interrupt = false
95
+
96
+ super
97
+ end
98
+
99
+ # Parameters used for generating C code to
100
+ # implement this bus device.
101
+ def memory
102
+ nil
103
+ end
104
+
105
+ # Setup to be performed on initialising the emulator
106
+ # instance.
107
+ def when_setup
108
+ ""
109
+ end
110
+
111
+ # Memory bus write handler.
112
+ #
113
+ # Handles write access via the memory bus to this device.
114
+ #
115
+ # @param addr The address being accessed.
116
+ # @param value The value being written.
117
+ def mem_write(addr, value)
118
+ end
119
+
120
+ # Memory bus read handler.
121
+ #
122
+ # Handles read access via the memory bus to this device.
123
+ #
124
+ # @param addr The address being accessed.
125
+ #
126
+ # Returns the value read, or nil if no value
127
+ # (e.g. if address falls outside range for this device).
128
+ def mem_read(addr)
129
+ nil
130
+ end
131
+
132
+ # IO bus write handler.
133
+ #
134
+ # Handles write access via the IO bus to this device.
135
+ #
136
+ # @param port The IO port being accessed.
137
+ # @param value The value being written.
138
+ def io_write(port, value)
139
+ end
140
+
141
+ # IO bus read handler.
142
+ #
143
+ # Handles read access via the IO bus to this device.
144
+ #
145
+ # @param port The IO port being accessed.
146
+ #
147
+ # Returns the value read from the port, or nil if no
148
+ # value (e.g. port does not correspond to this device).
149
+ def io_read(port)
150
+ nil
151
+ end
152
+
153
+ # Clock handler.
154
+ #
155
+ # Handles a clock cycle for this device.
156
+ # Deriving objects can use the nmi function
157
+ # to set the state of the non-maskable interrupt
158
+ # at each clock cycle.
159
+ def clock(cycles)
160
+ end
161
+
162
+ # FFI functions provided by this device.
163
+ def functions
164
+ []
165
+ end
166
+
167
+ # Sets state of the NMI for this device.
168
+ def nmi(state)
169
+ @nmi = state
170
+ end
171
+
172
+ # Gets state of NMI for this device.
173
+ def nmi?
174
+ @nmi
175
+ end
176
+
177
+ # Sets state of INT for this device.
178
+ def interrupt(state)
179
+ @interrupt = state
180
+ end
181
+
182
+ # Gets state of INT for this device.
183
+ def interrupt?
184
+ @interrupt
185
+ end
186
+
187
+ # Parameters for a bus device.
188
+ def params
189
+ %w(name)
190
+ end
191
+ end
192
+
78
193
  # Memory object.
79
194
  #
80
195
  # This is an abstract class from which all other memory objects inherit.
81
- class Memory < ConfigObject
196
+ class Memory < BusDevice
82
197
  # Constructor.
83
198
  #
84
199
  # Do not use, as this is an abstract class. Use one of the subclasses instead.
@@ -107,15 +222,62 @@ module Zemu
107
222
  end
108
223
  end
109
224
 
110
- # @return [Boolean] true if this memory section is readonly, false otherwise.
225
+ # Is this memory read-only?
111
226
  def readonly?
112
- return false
227
+ false
228
+ end
229
+
230
+ # Parameters used for generating C code to
231
+ # implement this memory block.
232
+ def memory
233
+ contents_initializer = ""
234
+ @contents.each_slice(32) do |c|
235
+ contents_initializer += c.map { |b| b.to_s }.join(", ")
236
+ contents_initializer += ",\n"
237
+ end
238
+
239
+ m = OpenStruct.new
240
+
241
+ m.setup = <<eos
242
+ zuint8 memory_#{name}[#{size}] =
243
+ {
244
+ #{contents_initializer}
245
+ };
246
+ eos
247
+
248
+ m.address = address
249
+ m.size = size
250
+ m.access_read = "memory_#{name}"
251
+ m.access_write = "memory_#{name}"
252
+
253
+ m
254
+ end
255
+
256
+ # Memory bus read handler.
257
+ #
258
+ # Handles read access via the memory bus to this device.
259
+ #
260
+ # @param addr The address being accessed.
261
+ #
262
+ # Returns the value read, or nil if no value
263
+ # (e.g. if address falls outside range for this device).
264
+ def mem_read(addr)
265
+ nil
266
+ end
267
+
268
+ # Memory bus write handler.
269
+ #
270
+ # Handles write access via the memory bus to this device.
271
+ #
272
+ # @param addr The address being accessed.
273
+ # @param value The value being written.
274
+ def mem_write(addr, value)
113
275
  end
114
276
 
115
277
  # Valid parameters for this object.
116
278
  # Should be extended by subclasses but NOT REPLACED.
117
279
  def params
118
- return %w(name address size)
280
+ super + %w(address size)
119
281
  end
120
282
 
121
283
  # Reads the contents of a file in binary format and
@@ -155,8 +317,14 @@ module Zemu
155
317
  super
156
318
  end
157
319
 
320
+ # Is this memory block readonly?
158
321
  def readonly?
159
- return true
322
+ true
323
+ end
324
+
325
+ # Memory write handler.
326
+ def mem_write(addr, port)
327
+ # Does nothing - cannot write to read-only memory.
160
328
  end
161
329
  end
162
330
 
@@ -165,169 +333,13 @@ module Zemu
165
333
  # Represents a block of memory which can be read and written.
166
334
  class RAM < Memory
167
335
  end
168
-
169
- # Input/Output Port object
170
- #
171
- # Represents an input/output device assigned to one or more ports.
172
- #
173
- # This is an abstract class and cannot be instantiated directly.
174
- # The when_setup, when_read, and when_write methods can be used to define
175
- # the behaviour of a subclass.
176
- #
177
- # @example
178
- # class MyIODevice < IOPort
179
- # # Extend the parameters of the object so we can define a port.
180
- # def params
181
- # super + "port"
182
- # end
183
- #
184
- # def initialize
185
- # super
186
- #
187
- # # Define the setup for the IO device.
188
- # # This is some global C code that ends up in "io.c".
189
- # # Parameters can be used here, as the block is instance-evaluated.
190
- # when_setup do
191
- # %Q(zuint8 #{name}_value = 42;)
192
- # end
193
- #
194
- # # Define the logic when reading from an IO port.
195
- # # The C variable "port" takes the value of the 8-bit port
196
- # # address being read from, and should be used to identify
197
- # # if this IO device is the one being used.
198
- # when_read do
199
- # %Q(if (port == #{port}) return #{name}_value;)
200
- # end
201
- #
202
- # # Define the logic when writing to the IO port.
203
- # # Similar to #when_read, but we have access to an extra
204
- # # C variable, "value". This is the value being written
205
- # # to the IO port.
206
- # when_write do
207
- # %Q(if (port == #{port}) #{name}_value = value;)
208
- # end
209
- # end
210
- # end
211
- #
212
- # # The subclass can now be declared as below:
213
- # device = MyIODevice.new do
214
- # name "myDevice"
215
- # port 11
216
- # end
217
- #
218
- #
219
- class IOPort < ConfigObject
220
- attr_reader :io_type
221
-
222
- # Constructor.
223
- #
224
- # Do not use, as this is an abstract class. Use one of the subclasses instead.
225
- def initialize
226
- if self.class == Zemu::Config::IOPort
227
- raise NotImplementedError, "Cannot construct an instance of the abstract class Zemu::Config::IOPort."
228
- end
229
-
230
- @ports = []
231
- @setup_block = nil
232
- @read_block = nil
233
- @write_block = nil
234
- @clock_block = nil
235
-
236
- super
237
- end
238
-
239
- # Defines the setup behaviour of this IO device.
240
- #
241
- # Expects a block, the return value of which is a string
242
- # containing all data and function declarations required by this IO device.
243
- #
244
- # The block will be instance-evaluated at build-time, so it is possible to use
245
- # instance variables of the IO device.
246
- def when_setup(&block)
247
- @setup_block = block
248
- end
249
-
250
- # Defines the read behaviour of this IO device.
251
- #
252
- # Expects a block, the return value of which is a string
253
- # containing the behaviour of this IO device when a value is read from the IO bus.
254
- # Care must be taken to ensure that this functionality does not conflict with that of
255
- # any other IO devices.
256
- #
257
- # The block will be instance-evaluated at build-time, so it is possible to use
258
- # instance variables of the IO device.
259
- def when_read(&block)
260
- @read_block = block
261
- end
262
-
263
- # Defines the write behaviour of this IO device.
264
- #
265
- # Expects a block, the return value of which is a string
266
- # containing the behaviour of this IO device when a value is written to the IO bus.
267
- # Care must be taken to ensure that this functionality does not conflict with that of
268
- # any other IO devices.
269
- #
270
- # The block will be instance-evaluated at build-time, so it is possible to use
271
- # instance variables of the IO device.
272
- def when_write(&block)
273
- @write_block = block
274
- end
275
-
276
- # Defines the per-cycle behaviour of this IO device.
277
- #
278
- # Expects a block, the return value of which is a string
279
- # defining the behaviour of the IO device on each system clock cycle.
280
- # Care must be taken to ensure that this functionality does not conflict with that of
281
- # any other IO devices.
282
- #
283
- # The block will be instance-evaluated at build-time, so it is possible to use
284
- # instance variables of the IO device.
285
- def when_clock(&block)
286
- @clock_block = block
287
- end
288
-
289
- # Evaluates the when_setup block of this IO device and returns the resulting string.
290
- def setup
291
- return instance_eval(&@setup_block) unless @setup_block.nil?
292
- return ""
293
- end
294
-
295
- # Evaluates the when_read block of this IO device and returns the resulting string.
296
- def read
297
- return instance_eval(&@read_block) unless @read_block.nil?
298
- return ""
299
- end
300
-
301
- # Evaluates the when_write block of this IO device and returns the resulting string.
302
- def write
303
- return instance_eval(&@write_block) unless @write_block.nil?
304
- return ""
305
- end
306
-
307
- # Evaluates the when_clock block of this IO device and returns the resulting string.
308
- def clock
309
- return instance_eval(&@clock_block) unless @clock_block.nil?
310
- return ""
311
- end
312
-
313
- # Defines FFI API which will be available to the instance wrapper if this IO device is used.
314
- def functions
315
- []
316
- end
317
-
318
- # Valid parameters for this object.
319
- # Should be extended by subclasses but NOT REPLACED.
320
- def params
321
- %w(name)
322
- end
323
- end
324
336
 
325
337
  # Serial Input/Output object
326
338
  #
327
339
  # Represents a serial connection between the emulated CPU
328
340
  # and the host machine, with input and output mapped to Z80 I/O
329
341
  # ports.
330
- class SerialPort < IOPort
342
+ class SerialPort < BusDevice
331
343
  # Constructor.
332
344
  #
333
345
  # Takes a block in which the parameters of the serial port
@@ -348,92 +360,93 @@ module Zemu
348
360
  def initialize
349
361
  super
350
362
 
351
- when_setup do
352
- "SerialBuffer io_#{name}_buffer_master = { .head = 0, .tail = 0 };\n" +
353
- "SerialBuffer io_#{name}_buffer_slave = { .head = 0, .tail = 0 };\n" +
354
- "\n" +
355
- "zusize zemu_io_#{name}_buffer_size(void)\n" +
356
- "{\n" +
357
- " zusize start = io_#{name}_buffer_slave.head;\n" +
358
- " zusize end = io_#{name}_buffer_slave.tail\n;" +
359
- " if (end < start) end += ZEMU_IO_SERIAL_BUFFER_SIZE;\n" +
360
- " return end - start;\n" +
361
- "}\n" +
362
- "\n" +
363
- "void zemu_io_#{name}_slave_puts(zuint8 val)\n" +
364
- "{\n" +
365
- " io_#{name}_buffer_slave.buffer[io_#{name}_buffer_slave.tail] = val;\n" +
366
- " io_#{name}_buffer_slave.tail++;\n" +
367
- " if (io_#{name}_buffer_slave.tail >= ZEMU_IO_SERIAL_BUFFER_SIZE)\n" +
368
- " io_#{name}_buffer_slave.tail = 0;\n" +
369
- "}\n" +
370
- "\n" +
371
- "zuint8 zemu_io_#{name}_slave_gets(void)\n" +
372
- "{\n" +
373
- " zuint8 val = io_#{name}_buffer_master.buffer[io_#{name}_buffer_master.head];\n" +
374
- " io_#{name}_buffer_master.head++;\n" +
375
- " if (io_#{name}_buffer_master.head >= ZEMU_IO_SERIAL_BUFFER_SIZE)\n" +
376
- " io_#{name}_buffer_master.head = 0;\n" +
377
- "\n" +
378
- " return val;\n" +
379
- "}\n" +
380
- "\n" +
381
- "void zemu_io_#{name}_master_puts(zuint8 val)\n" +
382
- "{\n" +
383
- " io_#{name}_buffer_master.buffer[io_#{name}_buffer_master.tail] = val;\n" +
384
- " io_#{name}_buffer_master.tail++;\n" +
385
- " if (io_#{name}_buffer_master.tail >= ZEMU_IO_SERIAL_BUFFER_SIZE)\n" +
386
- " io_#{name}_buffer_master.tail = 0;\n" +
387
- "}\n" +
388
- "\n" +
389
- "zuint8 zemu_io_#{name}_master_gets(void)\n" +
390
- "{\n" +
391
- " zuint8 val = io_#{name}_buffer_slave.buffer[io_#{name}_buffer_slave.head];\n" +
392
- " io_#{name}_buffer_slave.head++;\n" +
393
- " if (io_#{name}_buffer_slave.head >= ZEMU_IO_SERIAL_BUFFER_SIZE)\n" +
394
- " io_#{name}_buffer_slave.head = 0;\n" +
395
- "\n" +
396
- " return val;\n" +
397
- "}\n"
398
- end
363
+ @buffer_tx = []
364
+ @buffer_rx = []
365
+ end
399
366
 
400
- when_read do
401
- "if (port == #{in_port})\n" +
402
- "{\n" +
403
- " return zemu_io_#{name}_slave_gets();\n" +
404
- "}\n" +
405
- "else if (port == #{ready_port})\n" +
406
- "{\n" +
407
- " if (io_#{name}_buffer_master.head == io_#{name}_buffer_master.tail)\n" +
408
- " {\n" +
409
- " return 0;\n" +
410
- " }\n" +
411
- " else\n" +
412
- " {\n" +
413
- " return 1;\n" +
414
- " }\n" +
415
- "}\n"
367
+ # IO bus read handler.
368
+ #
369
+ # Handles read access via the IO bus to this device.
370
+ #
371
+ # @param port The IO port being accessed.
372
+ #
373
+ # Returns the value read, or nil if the port does not
374
+ # correspond to this device.
375
+ def io_read(port)
376
+ if port == in_port
377
+ return @buffer_rx.shift()
378
+ elsif port == ready_port
379
+ if @buffer_rx.empty?
380
+ return 0
381
+ else
382
+ return 1
383
+ end
416
384
  end
417
385
 
418
- when_write do
419
- "if (port == #{out_port})\n" +
420
- "{\n" +
421
- " zemu_io_#{name}_slave_puts(value);\n" +
422
- "}\n"
386
+ nil
387
+ end
388
+
389
+ # IO bus write handler.
390
+ #
391
+ # Handles write access via the IO bus to this device.
392
+ #
393
+ # @param port The IO port being accessed.
394
+ # @param value The value being written.
395
+ def io_write(port, value)
396
+ if port == out_port
397
+ @buffer_tx << value
423
398
  end
424
399
  end
425
400
 
426
- # Defines FFI API which will be available to the instance wrapper if this IO device is used.
427
- def functions
428
- [
429
- {"name" => "zemu_io_#{name}_master_puts".to_sym, "args" => [:uint8], "return" => :void},
430
- {"name" => "zemu_io_#{name}_master_gets".to_sym, "args" => [], "return" => :uint8},
431
- {"name" => "zemu_io_#{name}_buffer_size".to_sym, "args" => [], "return" => :uint64}
432
- ]
401
+ # Gets number of bytes transmitted by the CPU,
402
+ # but not yet read from this device.
403
+ def transmitted_count
404
+ @buffer_tx.size
405
+ end
406
+
407
+ # Gets a byte transmitted by the CPU, or nil
408
+ # if transmit buffer is empty.
409
+ def get_byte
410
+ @buffer_tx.shift()
411
+ end
412
+
413
+ # Puts a byte in the receive buffer of this device.
414
+ def put_byte(b)
415
+ @buffer_rx << b
416
+ end
417
+
418
+ # Puts a string in the receive buffer of this device.
419
+ def puts(s)
420
+ s.each_byte { |b| put_byte(b) }
421
+ end
422
+
423
+ # Gets a string from the transmit buffer of this device.
424
+ # String length will be no more than n, but may be less
425
+ # if fewer characters exist in buffer.
426
+ #
427
+ # @param n Length of string to retrieve. If omitted the
428
+ # entire buffer will be returned.
429
+ def gets(n=nil)
430
+ s = ""
431
+
432
+ if n.nil?
433
+ until (c = get_byte()).nil?
434
+ s += c.chr
435
+ end
436
+ else
437
+ n.times do
438
+ c = get_byte()
439
+ break if c.nil?
440
+
441
+ s += c.chr
442
+ end
443
+ end
444
+
445
+ s
433
446
  end
434
447
 
435
448
  # Valid parameters for a SerialPort, along with those
436
- # defined in [Zemu::Config::IOPort].
449
+ # defined in [Zemu::Config::BusDevice].
437
450
  def params
438
451
  super + %w(in_port out_port ready_port)
439
452
  end
@@ -443,7 +456,16 @@ module Zemu
443
456
  #
444
457
  # Represents a device with a sequence of sectors of a fixed size,
445
458
  # which can be accessed via IO instructions as an IDE drive.
446
- class BlockDrive < IOPort
459
+ class BlockDrive < BusDevice
460
+ # Mode for reading drive.
461
+ DRIVE_MODE_READ = 0x01
462
+
463
+ # Mode for writing drive.
464
+ DRIVE_MODE_WRITE = 0x02
465
+
466
+ # Uninitialised drive mode.
467
+ DRIVE_MODE_UNINIT = 0x00
468
+
447
469
  # Constructor.
448
470
  #
449
471
  # Takes a block in which the parameters of the block drive
@@ -479,131 +501,110 @@ module Zemu
479
501
  end
480
502
  end
481
503
 
482
- when_setup do
483
- <<-eos
484
- #include <stdio.h>
504
+ @lba_0 = 0
505
+ @lba_1 = 0
506
+ @lba_2 = 0
507
+ @lba_3 = 0
485
508
 
486
- zuint8 sector_data_#{name}[#{sector_size}];
487
- zuint32 sector_data_#{name}_offset;
488
- zuint32 loaded_sector_#{name} = Z_UINT32_MAXIMUM;
489
- zuint8 drive_mode_#{name};
490
- zuint8 drive_status_#{name} = 0b01000000;
509
+ @drive_mode = DRIVE_MODE_UNINIT
510
+ @drive_status = 0b01000000
511
+ @sector_offset = 0
512
+ @sector_data = []
513
+ end
491
514
 
492
- zuint8 lba_#{name}_0;
493
- zuint8 lba_#{name}_1;
494
- zuint8 lba_#{name}_2;
495
- zuint8 lba_#{name}_3;
515
+ # IO bus read handler.
516
+ #
517
+ # Handles read access via the IO bus to this device.
518
+ #
519
+ # @param port The IO port being accessed.
520
+ #
521
+ # Returns the value read, or nil if the port does not
522
+ # correspond to this device.
523
+ def io_read(port)
524
+ if port == base_port
525
+ b = @sector_data.shift()
526
+
527
+ if @sector_data.empty?
528
+ @drive_status = 0b01000000
529
+ end
496
530
 
497
- void internal_#{name}_load_sector(zuint32 sector)
498
- {
499
- if (loaded_sector_#{name} == sector) return;
531
+ return b
532
+ elsif port == (base_port + 7)
533
+ return @drive_status
534
+ end
500
535
 
501
- FILE * fptr = fopen("#{@initialize_from}", "rb");
502
- fseek(fptr, sector * #{sector_size}, SEEK_SET);
503
- fread(sector_data_#{name}, #{sector_size}, 1, fptr);
504
- fclose(fptr);
536
+ nil
537
+ end
505
538
 
506
- loaded_sector_#{name} = sector;
507
- }
539
+ # Get a sector number from the four LBA registers.
540
+ def get_sector()
541
+ sector = 0
542
+ sector |= @lba_0
543
+ sector |= @lba_1 << 8
544
+ sector |= @lba_2 << 16
545
+ sector |= @lba_3 << 24
508
546
 
509
- void internal_#{name}_write_current_sector()
510
- {
511
- FILE * fptr = fopen("#{@initialize_from}", "r+b");
512
- fseek(fptr, loaded_sector_#{name} * #{sector_size}, SEEK_SET);
513
- fwrite(sector_data_#{name}, 1, #{sector_size}, fptr);
514
- fclose(fptr);
515
- }
547
+ sector
548
+ end
516
549
 
517
- zuint8 zemu_io_#{name}_readbyte(zuint32 sector, zuint32 offset)
518
- {
519
- internal_#{name}_load_sector(sector);
520
- return sector_data_#{name}[offset];
521
- }
522
- eos
550
+ # Write sector data to the sector currently pointed
551
+ # to by the LBA registers.
552
+ def write_current_sector()
553
+ file_offset = get_sector() * sector_size
554
+ File.open(@initialize_from, "r+b") do |f|
555
+ f.seek(file_offset)
556
+ f.write(@sector_data.pack("C" * sector_size))
523
557
  end
558
+ end
524
559
 
525
- when_read do
526
- <<-eos
527
- if (port == #{base_port})
528
- {
529
- zuint8 b = sector_data_#{name}[sector_data_#{name}_offset];
530
- sector_data_#{name}_offset++;
531
- if (sector_data_#{name}_offset >= #{sector_size})
532
- {
533
- drive_status_#{name} = 0b01000000;
534
- }
535
- return b;
536
- }
537
- else if (port == #{base_port+7})
538
- {
539
- return drive_status_#{name};
540
- }
541
- eos
560
+ # Load sector data pointed to by LBA registers.
561
+ def load_sector()
562
+ file_offset = get_sector() * sector_size
563
+ File.open(@initialize_from, "rb") do |f|
564
+ f.seek(file_offset)
565
+ s = f.read(sector_size)
566
+ @sector_data = s.unpack("C" * sector_size)
542
567
  end
568
+ end
543
569
 
544
- when_write do
545
- <<-eos
546
- if (port == #{base_port})
547
- {
548
- if (drive_mode_#{name} == 0x01)
549
- {
550
- sector_data_#{name}[sector_data_#{name}_offset] = value;
551
- sector_data_#{name}_offset++;
552
- if (sector_data_#{name}_offset >= #{sector_size})
553
- {
554
- internal_#{name}_write_current_sector();
555
- drive_status_#{name} = 0b01000000;
556
- }
557
- }
558
- }
559
- else if (port == #{base_port+3})
560
- {
561
- lba_#{name}_0 = value;
562
- }
563
- else if (port == #{base_port+4})
564
- {
565
- lba_#{name}_1 = value;
566
- }
567
- else if (port == #{base_port+5})
568
- {
569
- lba_#{name}_2 = value;
570
- }
571
- else if (port == #{base_port+6})
572
- {
573
- lba_#{name}_3 = value & 0b00011111;
574
- }
575
- else if (port == #{base_port+7})
576
- {
577
- if (value == 0x20)
578
- {
579
- zuint32 sector = 0;
580
- sector |= (zuint32)lba_#{name}_3 << 24;
581
- sector |= (zuint32)lba_#{name}_2 << 16;
582
- sector |= (zuint32)lba_#{name}_1 << 8;
583
- sector |= (zuint32)lba_#{name}_0;
584
-
585
- internal_#{name}_load_sector(sector);
586
- sector_data_#{name}_offset = 0;
587
-
588
- drive_mode_#{name} = 0x00;
589
- drive_status_#{name} = 0b00001000;
590
- }
591
- else if (value == 0x30)
592
- {
593
- zuint32 sector = 0;
594
- sector |= (zuint32)lba_#{name}_3 << 24;
595
- sector |= (zuint32)lba_#{name}_2 << 16;
596
- sector |= (zuint32)lba_#{name}_1 << 8;
597
- sector |= (zuint32)lba_#{name}_0;
598
-
599
- internal_#{name}_load_sector(sector);
600
- sector_data_#{name}_offset = 0;
601
-
602
- drive_mode_#{name} = 0x01;
603
- drive_status_#{name} = 0b00001000;
604
- }
605
- }
606
- eos
570
+ # IO bus write handler.
571
+ #
572
+ # Handles write access via the IO bus to this device.
573
+ #
574
+ # @param port The IO port being accessed.
575
+ # @param value The value being written.
576
+ def io_write(port, value)
577
+ if port == base_port
578
+ if @drive_mode == DRIVE_MODE_WRITE
579
+ @sector_data << value
580
+ if @sector_data.size >= sector_size
581
+ write_current_sector()
582
+ @drive_status = 0b01000000
583
+ end
584
+ end
585
+ elsif port == (base_port + 3)
586
+ @lba_0 = (value & 0xff)
587
+ elsif port == (base_port + 4)
588
+ @lba_1 = (value & 0xff)
589
+ elsif port == (base_port + 5)
590
+ @lba_2 = (value & 0xff)
591
+ elsif port == (base_port + 6)
592
+ @lba_3 = (value & 0x1f)
593
+ elsif port == (base_port + 7)
594
+ # Read command.
595
+ if value == 0x20
596
+ load_sector()
597
+
598
+ @drive_mode = DRIVE_MODE_READ
599
+ @drive_status = 0b00001000
600
+
601
+ # Write command.
602
+ elsif value == 0x30
603
+ @sector_data = []
604
+
605
+ @drive_mode = DRIVE_MODE_WRITE
606
+ @drive_status = 0b00001000
607
+ end
607
608
  end
608
609
  end
609
610
 
@@ -637,15 +638,29 @@ eos
637
638
  @initialize_from = file
638
639
  end
639
640
 
641
+ # Read a byte at the given offset in a sector.
642
+ #
643
+ # @param sector The sector to read from.
644
+ # @param offset Offset in that sector to read.
645
+ #
646
+ # Returns the byte read from the file.
647
+ def read_byte(sector, offset)
648
+ file_offset = (sector * sector_size) + offset
649
+ File.open(@initialize_from, "rb") do |f|
650
+ f.seek(file_offset)
651
+ s = f.read(1)
652
+ return s.unpack("C")[0]
653
+ end
654
+ end
655
+
640
656
  # Defines FFI API which will be available to the instance wrapper if this IO device is used.
641
657
  def functions
642
658
  [
643
- {"name" => "zemu_io_#{name}_readbyte", "args" => [:uint32, :uint32], "return" => :uint8},
644
659
  ]
645
660
  end
646
661
 
647
662
  # Valid parameters for a BlockDrive, along with those
648
- # defined in [Zemu::Config::IOPort].
663
+ # defined in [Zemu::Config::BusDevice].
649
664
  def params
650
665
  super + %w(base_port sector_size num_sectors)
651
666
  end
@@ -656,29 +671,45 @@ eos
656
671
  # Represents a timer device, the period of which can be controlled
657
672
  # by the CPU through an IO port. The timer generates an NMI once this
658
673
  # period has expired. The timer can be reset via a control port.
659
- class Timer < IOPort
660
- def initialize
661
- super
674
+ class Timer < BusDevice
675
+ # Timer is running - count decrements once per clock cycle.
676
+ RUNNING = 0x01
662
677
 
663
- when_setup do
664
- "zuint8 io_#{name}_count;\n" +
665
- "zuint8 io_#{name}_running = 0;\n"
666
- end
678
+ # Timer is stopped - count does not change on clock cycle.
679
+ STOPPED = 0x00
667
680
 
668
- when_read do
669
- end
681
+ def initialize
682
+ super
670
683
 
671
- when_write do
672
- "if (port == #{count_port}) io_#{name}_count = value;\n" +
673
- "else if (port == #{control_port}) io_#{name}_running = value;\n"
684
+ @count = 0
685
+ @running = false
686
+ end
687
+
688
+ # IO bus write handler.
689
+ #
690
+ # Handles write access via the IO bus to this device.
691
+ #
692
+ # @param port The IO port being accessed.
693
+ # @param value The value being written.
694
+ def io_write(port, value)
695
+ if port == count_port
696
+ @count = value
697
+ elsif port == control_port
698
+ @running = if value == 0 then STOPPED else RUNNING end
674
699
  end
700
+ end
675
701
 
676
- when_clock do
677
- "if (io_#{name}_running)\n" +
678
- "{\n" +
679
- " if (io_#{name}_count > 0) io_#{name}_count--;\n" +
680
- " else zemu_io_nmi(instance);\n" +
681
- "}\n"
702
+ # Clock handler.
703
+ #
704
+ # Handles a clock cycle for this device.
705
+ # Sets NMI active if the count reaches 0.
706
+ def clock(cycles)
707
+ if @running == RUNNING
708
+ if @count > 0
709
+ @count -= cycles
710
+ else
711
+ nmi(true)
712
+ end
682
713
  end
683
714
  end
684
715
 
@@ -694,11 +725,8 @@ eos
694
725
  return binding
695
726
  end
696
727
 
697
- # The memory sections of this configuration object.
698
- attr_reader :memory
699
-
700
- # The IO devices of this configuration object.
701
- attr_reader :io
728
+ # The bus devices of this configuration object.
729
+ attr_reader :devices
702
730
 
703
731
  # Parameters accessible by this configuration object.
704
732
  def params
@@ -737,8 +765,7 @@ eos
737
765
  #
738
766
  # @raise [Zemu::ConfigError] Raised if the +name+ parameter is not set, or contains whitespace.
739
767
  def initialize
740
- @memory = []
741
- @io = []
768
+ @devices = []
742
769
 
743
770
  super
744
771
 
@@ -753,16 +780,29 @@ eos
753
780
 
754
781
  # Adds a new memory section to this configuration.
755
782
  #
783
+ # Deprecated - retained only for backwards compatibility.
784
+ # Use add_device instead.
785
+ #
756
786
  # @param [Zemu::Config::Memory] mem The memory object to add.
757
787
  def add_memory(mem)
758
- @memory << mem
788
+ @devices << mem
759
789
  end
760
790
 
761
791
  # Adds a new IO device to this configuration.
762
792
  #
763
- # @param [Zemu::Config::IOPort] io The IO device to add.
793
+ # Deprecated - retained only for backwards compatibility.
794
+ # Use add_device instead.
795
+ #
796
+ # @param [Zemu::Config::BusDevice] io The IO device to add.
764
797
  def add_io(io)
765
- @io << io
798
+ @devices << io
799
+ end
800
+
801
+ # Adds a new device to the bus for this configuration.
802
+ #
803
+ # @param [Zemu::Config::BusDevice] device The device to add.
804
+ def add_device(device)
805
+ @devices << device
766
806
  end
767
807
  end
768
808