zemu 0.4.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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