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 address 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 address 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.