on
Retro Challenge 2024 #5
Introduction
As a last resort to get the SDRAM working, I used the Gowin SDRAM controller IP. This is an encrypted binary blob and is essentially a black box. The embedded variant for the GW1NR SDRAM has no configuration options so we can’t learn anything from that.
As an experiment, I made a separate project that writes a 16-bit counter to address 0 of the SDRAM, reads it back and displays it on the LEDs of the TEC0117 board, see here. Finally something that works!
The Gowin IP drives the internal SDRAM via signals that we can spy on. The GW68000 project already has a transmit UART so I used that to send information to the PC. A state machine pulses the clock input of the SDRAM controller and records the signals going to the SDRAM in four bytes. This data is prepended by “AA55” hexadecimal as a synchronisation word and an 8-bit counter, to check for lost packets.
A simple python program turns the received bits into human readable information:
000 97 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
001 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
002 12 FF CF addr=FFF cmd=PRECHRG dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
003 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
004 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
005 11 FF CF addr=FFF cmd=REFRESH dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
006 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
007 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
008 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
009 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
010 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
011 11 FF CF addr=FFF cmd=REFRESH dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
012 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
013 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
014 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
015 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
016 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
017 10 30 00 addr=030 cmd=MODE dqm=0 ba=0 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
MODE: burst length=0 cas latency=3
018 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
019 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
020 17 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
021 97 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
022 97 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=0
023 97 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
024 97 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=1 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
025 97 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=1 rd_valid=0 wrd_ack=1 rd_n=1 wr_n=1
026 97 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
027 93 00 00 addr=000 cmd=ACTIVE dqm=0 ba=0 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
028 97 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
029 84 00 00 addr=000 cmd=WRITE dqm=0 ba=0 dqdrive=0 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
030 87 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=0 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
031 87 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=0 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
032 82 FF CF addr=FFF cmd=PRECHRG dqm=0 ba=3 dqdrive=0 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
033 87 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=0 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
034 87 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=0 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
035 87 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=0 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
036 87 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=0 busy_n=1 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
037 87 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=0 busy_n=1 rd_valid=0 wrd_ack=0 rd_n=0 wr_n=1
038 87 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=0 busy_n=1 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
039 87 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=0 busy_n=1 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
040 87 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=0 busy_n=1 rd_valid=0 wrd_ack=1 rd_n=1 wr_n=1
041 87 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=0 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
042 83 00 00 addr=000 cmd=ACTIVE dqm=0 ba=0 dqdrive=0 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
043 87 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=0 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
044 95 00 00 addr=000 cmd=READ dqm=0 ba=0 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
045 92 FF CF addr=FFF cmd=PRECHRG dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
046 97 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
047 97 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
048 97 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=0 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
049 97 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=1 rd_valid=1 wrd_ack=0 rd_n=1 wr_n=1
050 97 FF CF addr=FFF cmd=NOP dqm=0 ba=3 dqdrive=1 busy_n=1 rd_valid=0 wrd_ack=0 rd_n=1 wr_n=1
The Gowin SDRAM controller starts by initialising the SDRAM and writing the mode register. The most important information is the CAS latency, which is set to 3 cycles. Some information about timing can be extacted by counting the number of cycles between PRECHARGE and/or ACTIVE operations, taking into account this controller was probably designed for 100 MHz SDRAM clock.
The dqdrive
, busy_n
, rd_valid
and wrd_ack
signals are generated by the SDRAM controller, not by the SDRAM itself. The rd_n
and wr_n
are generated by the top level state machine. Signals going to the SDRAM directly are addr
, cmd
, dqm
and ba
, where cmd
is a 3-bit signal comprising ras_n
, cas_n
and we_n
(MSB to LSB).
Both the write and read sequence start with a ACTIVE
command where the 12-bit addr
ess holds the row to be activated. The 2-bit ba
selects the RAM bank. The read or write command provides the column, again via the addr
ess signal. The sequence is terminated by a PRECHARGE
command which writes back the information to memory. In the case of a read sequence, the data is output four cycles after the PRECHARGE
command (see line number 049).
The code for the capture gateware is on Github.
Conclusion
Now that the SDRAM is working with the Gowin IP and the signal sequences are known, it’s time to write our own SDRAM controller that better fits the GW68000 system.