MJoergen / C64MEGA65

Commodore 64 core for the MEGA65 based on the MiSTer FPGA C64 core
GNU General Public License v3.0
23 stars 4 forks source link

Support a F83 RTC and test it on GEOS #103

Closed sy2002 closed 5 months ago

sy2002 commented 8 months ago

@MJoergen Please decide if you consider this as a long hanging fruit for 5.1. If not, then remove the 5.1 surge label.

This issue needs the following M2M hardware abstraction first, so that the three different RTCs on the MEGA65 are abstracted away from the C64 core (and so that other cores such as the ZX Spectrum can leverage the RTC, too):

https://github.com/sy2002/MiSTer2MEGA65/issues/29

Information on how to support an RTC on the C64

MJoergen commented 8 months ago

My understanding is the following:

The GEOS communicates on the cassette port and expects an F83 RTC device.

In other words, GEOS expects to be communicating with an I2C device with a specific address and a specific memory map.

Therefore, the M2M framework would need to emulate a "virtual" F83 RTC device, while at the same time communicating with the "real" RTC on the board. Essentially, the M2M framework would be the "man-in-the-middle".

MJoergen commented 8 months ago

However, much simpler is to re-use MiSTer's emulation rtcF83.sv. This takes care of the I2C interface to GEOS.

The MiSTer emulation file expects a 65-bit signal containing the current date/time, with the following format (see file user_io.cpp in Main_MiSTer repo):

   -- Bits  7 -  0 : Seconds    (BCD format, 0x00-0x60)
   -- Bits 15 -  8 : Minutes    (BCD format, 0x00-0x59)
   -- Bits 23 - 16 : Hours      (BCD format, 0x00-0x23)
   -- Bits 31 - 24 : DayOfMonth (BCD format, 0x01-0x31)
   -- Bits 39 - 32 : Month      (BCD format, 0x01-0x12)
   -- Bits 47 - 40 : Year       (BCD format, 0x00-0x99)
   -- Bits 55 - 48 : DayOfWeek  (0x00-0x06)
   -- Bits 63 - 56 : 0x40
   -- Bit       64 : Toggle flag. Flips anytime there is a change in the other bits

The M2M framework would then continuously make sure this signal has the correct value, by appropriate communication with whatever RTC is present on the MEGA65 board.

MJoergen commented 8 months ago

After several attempts I have come to the conclusion that the following "hybrid" approach is best:

Firstly, the M2M framework (VHDL part) will provide a generic QNICE-memory-mapped interface to the I2C bus. In other words, via QNICE reads/writes to framework device ID C_DEV_I2C it is possible to initiate reads and writes to an arbitrary I2C device.

Secondly, the Core makes use of the same interface as on MiSTer, i.e. a 65-bit generic value describing the current date/time in BCD format. This value only needs to be presented once after reset, the core will maintain (i.e. keep up-to-date) its own local copy of the current date/time.

What is left (i.e. still not done), is a purely QNICE firmware task:

The benefit of this approach is that the same QNICE register map can be used from the monitor to debug / verify / communicate with (any) I2C devices on the board.

Since the next step is a firmware task, I'm reassigning to @sy2002

MJoergen commented 8 months ago

There should also be some menu options.

E.g. some users might actually have a real RTC connected via the tape port (just like on a "real" C64), while others would use the onboard RTC (i.e. emulate the GEOS-compatible RTC).

There is also the "confusion" about the "original" RTC on the R3 board, and a "modified" RTC. I don't know if it's possible to auto-detect which is the case. Probably, but if not, the user would have to make the selection.

Other TODOs for the VHDL part:

MJoergen commented 8 months ago

On the R4 board, the following sequence of operations will read out the date/time from the RTC:

  1. Write 0x0005 to 0xFFF4 (Select I2C device)
  2. Write 0x0000 to 0x7000 (Prepare to write to RTC)
  3. Write 0x01A2 to 0x70F0 (Send one byte, 0x00, to RTC)
  4. Read from 0x70F1 until value is 0x0001 (I2C is idle)
  5. Write 0x07A3 to 0x70F0 (Receive seven bytes from RTC)
  6. Read from 0x70F1 until value is 0x0001 (I2C is idle)
  7. The RTC date/time can now be read from 0x7000 to 0x7006.

Similar sequence (with few alterations) should work for both R3 variants.

MJoergen commented 8 months ago

One idea to auto-detect the RTC, is to monitor bit 2 of value 0x70F1 (Status.2 = NACK). This bit is set when there is no device responding.

MJoergen commented 8 months ago

Regarding detecting the board revision, I propose to use the SysInfo record. Specifically to introduce a new record: C_SYS_BOARD with value 0x0040. Then address 0 could be one of:

R3 : 0x0003 R4 : 0x0004 R5 : 0x0005

The VHDL part would then be responsible for populating the correct value (based on the generic G_BOARD).

MJoergen commented 8 months ago

The following lists the various I2C busses on the various board. For each bus there is a separate pair of SCL/SDA pins on the FPGA. The naming of the busses is from the schematic.

R3:

R4:

R5:

In order to support RTC on the R3 board, we must be able to communicate with both the "FPGA" bus (the internal RTC), and the "Grove" bus (the external RTC connected via PMOD).

sy2002 commented 8 months ago

@MJoergen : Makes sense for me - let's make it so. We already have a poll-loop in the firmware, so the firmware can rather easily ensure that the output signal stays updated. I am not sure if we should use control_d_o or if it makes sense to introduce a new, dedicated signal that is meant to be used for RTCs-only.

MJoergen commented 8 months ago

Since we agree so far, I've now merged the branch "mfj_rtc_support" into the "develop" branch.

MJoergen commented 8 months ago

Since this whole RTC feature is very much core-dependent, I was thinking about having the necessary QNICE firmware be placed in the core-specific file CORE/m2m-rom/m2m-rom.asm.

We could do that as a first step, and if we see a recurring pattern for other cores, then we can refactor as needed.

sy2002 commented 8 months ago

I am not fully getting why the firmware is core specific? If we had a new dedicated VHDL signal that is provided in main.vhd and that is for example formatted like the MiSTer format: Couldn't the CPP take it from there? And then the firmware would be non-core-specific? "Formatting" or "converting" this 64 bit std_logic_vector would be something that happened inside main.vhd (or mega65.vhd) and the firmware just make sure that it is updated at least 10x per second or so.

MJoergen commented 8 months ago

My bad. I agree with you. The process of reading the date/time from the (board-specific) RTC and converting to the "generic" date/time format is completely core-independent. It is only the subsequent processing of this generic date/time information from the framework, which is core-specific.

So the question remains, whether this should be presented as part of the control_d_o signal, or whether we should introduce a new dedicated signal. I guess the latter makes most sense.

MJoergen commented 8 months ago

Since this is our "first" use of I2C, there are some architectural choices to be made. Specifically, should we support e.g. the Audio DAC? The Audio DAC works differently on the three boards, and it's connected differently too (i.e. different I2C bus and different I2C address). If we want to support the Audio DAC, should the "knowledge" of connectivity of the Audio DAC (i.e. which I2C bus and I2C address to use) be placed in VHDL and abstracted away, or should the QNICE firmware be in full control?

Since we're currently only using the I2C interface for the RTC, this decision is conveniently postponed. But the current implementation provides no abstraction, and expects the QNICE firmware to be in full control / knowledge of which devices (e.g. Audio DAC) are connected to which I2C busses and with which I2C addresses.

sy2002 commented 8 months ago

If technically possible at all going forward (and I admittedly did not think it through) I would prefer a completely board-revision agnostic firmware: Everything should be abstracted away on the VHDL part of the house. Now... ...this is my wishful thinking. You @MJoergen decide what is feasible at all. And since I will not be able to work at this issue any time soon: Feel free to refactor everything you've done so far and yes, we should add a dedicated (64-bit?) signal for RTC to the framework.

MJoergen commented 8 months ago

I understand your point of view. This will require some thought, because there are many ways to do this. But I understand that the firmware just "wants" the date/time somehow, without having to worry about the implementation (i.e. board specific) details. This is really the same requirement the core has. If this is done completely, then the firmware need not be involved at all.

Thinking out loud: The VHDL code could implement a small state machine that (upon request, e.g. after reset) goes through a pre-defined (but board-specific) set of reads/writes. This will be interfacing with the RTC module over the I2C bus using the new QNICE-to-I2C wrapper (implemented in i2c_controller.vhd). In other words, we'll keep the QNICE register interface intact, because it is really useful for debugging.

So I will have three (or so) sets of QNICE accesses (read/writes), written in a small table, and fed into a small state machine. This entity would be called rtc_reader.vhd and connect to the QNICE device interface (i.e. acting as a second "master" on the QNICE device interface). The output from this file would be the 65-bit date/time signal that can be fed directly to the core.

The only thing missing here is how to arbitrate between this new rtc_reader and the existing QNICE code. I will need to write a QNICE-arbiter, that will delay one (or the other). For maximum robustness, the rtc_reader should not be interrupted while speaking with the I2C device, in case the QNICE firmware starts writing to e.g. M2M$RAMROM_DEV.

However, if I connect the arbiter only to the QNICE-2-I2C wrapper (instead of the entire general QNICE memory mapped I/O), then the QNICE firmware will not be impacted (assuming it is not itself speaking with the I2C devices). Then the memory location M2M$RAMROM_DEV will not be in use either.

In conclusion, that is entirely doable, and the work so far is not in vain. I'm re-assigning to myself again.

sy2002 commented 8 months ago

👍🏻 Sounds promising :-)

sy2002 commented 5 months ago

@MJoergen ✅ WORKS IN GEOS, see screenshot:

GEOS-RTC

One needs to install the RTC driver by copying the driver file on the local GEOS boot disk: https://github.com/mister-devel/C64_MiSTer/blob/master/releases/CP-ClockF83_1.3.D64