Need help debugging SDRAM controller - Page 1
Forum section: electronics / general

author: Dukov Ahzrukhal
author_rank: Regular Contributor
timestamp: October 13, 2020, 09:15:24 pm
content: Hello everyone, I have been working on a TRS-80 project and want to start using SDRAM. I wrote a simple controller that allows me to share the SDRAM between the CPU and display controller, but I can't get it to work. I wrote a test setup that is basically the entire TRS-80 but with an Arduino driving a dummy CPU module.The memory controller has two ports: port A (a read and write port for the CPU), and port B (a read only port for the display controller). After many hours of tweaking the SDRAM controller I realized that if I disable reading on port A the memory controller works almost perfectly (writing on port A works and reading port B works with occasional glitches).Reading on port A works exactly the same as reading port B, so this makes no sense to me. I have tried everything I can think of, from inserting more wait states everywhere to tweaking the clock phase of the SDRAM chip. Nothing works, every time I enable reading on port A reading any address on B gives the last data written to port A.I will attach my SDRAM controller file here, as well as the top level of my test setup and the Arduino code that controls it. The SDRAM chip I'm using is the HY57V641620FTP-H.Any help would be greatly appreciated.

author: asmi
author_rank: Super Contributor
timestamp: October 13, 2020, 10:24:20 pm
content: How do you expect us to help you? We can't run the simulation, as the "test" contains some irrelevant parts which we don't have a source code for, as for the actual memory controller, it looks like it's "development" involved quite a bit of copy-paste, and minimal (if any) efforts to make the code readable and understandable. HDL code in general is "famous" for being extremely hard to read and understand unless a lot of effort was invested into making it more reader-friendly. I highly recommend including a comment which will have rough state diagram and short description of what each state is supposed to represent. And this is coming from somewhere who has written quite a number of memory controllers, so I know what the data flow is supposed to be.As I was able to decipher from the code, it looks like you are not using read-with-precharge and write-with-precharge commands, even though it looks like you manually precharge a row after every operation (which is HORRIBLY inefficient way of doing things). Why is that?

author: Dukov Ahzrukhal
author_rank: Regular Contributor
timestamp: October 13, 2020, 10:38:19 pm
content: If you need additional information about the test setup I will gladly provide it. I take offence to your comment because there was very little copy paste involved in making that. If you can be more specific about what is making my code difficult to read I will gladly go back and clean it up.I am not using read and write with automatic precharge simply because from the data sheet it is not obvious how to do that (I don't know how many wait states I would need). If you can explain that to me I can change my code to work that way.I will attach the code for the display controller, in case it helps you. It provides the HSYNC signal to the memory controller, so refresh always happens when the display controller is not active.EDIT: I removed the commented out code (my failed attempts of getting this to work) and changed the code to use automatic precharge. It still fails in the exact same way if I enable reading in port A.I will attach the new file here.

author: asmi
author_rank: Super Contributor
timestamp: October 14, 2020, 12:46:28 am
content: Sorry, I didn't mean to offend anyone. It's just "I sing what I see, and I see what I sing" (C)Can you please provide a code for controller with that reading over port A enabled, so that I would be able to see the code which actually has a problem.Also, in general, all SDRAMs (including SDR and all DDRx ones) work the same way - you open a row, you work with it however long you need, then you close it. If at a time of issuing a read/write command you know for fact that you won't need to access that row anymore (or that you know that you will need to access a different row in the same bank), you can use auto precharge command. Also keep in mind that you can use different banks in parallel, as each bank can have it's own open row - just make sure you close them all before you do a refresh. You can so some very clever tricks with banks, for example - double-buffering - have your CPU write framebuffer into bank 1, while the video controller reads a previous frame off bank 0, then at some point you swap these around. Also consider using bursts at least for frame reads - this will give you much greater bandwidth and efficiency as you will better amortize initial access latency.Finally, I'd highly recommend you to read through Micron's datasheet for SDRAM:https://www.micron.com/products/dram/sdram/part-catalog/mt48lc32m8a2p-6aIt's more detailed as far as timings are concerned. But more importantly - on the page I've linked there is a verilog simulation model for their memory chips - this way you can verify in simulation that your controller code's behavior is correct without the need to debug it in hardware, which is more complicated and is much slower. Also Micron's sim model will report errors if any timing parameters are violated. I know you don't use Micron part on your board, but I suspect all SDRAM chips operate sufficiently similar to one another that it shouldn't matter as long as timing parameters are correct.

author: Dukov Ahzrukhal
author_rank: Regular Contributor
timestamp: October 14, 2020, 01:06:17 am
content: I'll be sure to look at the link you provided, as I certainly agree that it would be easier to debug this with a simulation. I will also attach the file with reading port A enabled here. It is almost identical to the previous one, with the only difference being in S_write_A (one line has been uncommented, and one line has been commented).I have another FPGA board with a similar SDRAM chip, If this works at all in a simulation I will test it on that board to make sure the issue not bad SDRAM chip.

author: hamster_nz
author_rank: Super Contributor
timestamp: October 14, 2020, 01:23:23 am
content: I mapped out the state transitionsCode:_reset => S_initS_init => S_init_NOPS_init_NOP => S_modeS_mode => S_mode_NOPS_mode_NOP => S_refresh_1S_activate_A => S_activate_A_NOPS_activate_A_NOP => S_write_AS_write_A => S_write_NOPS_write_NOP => S_activate_BS_precharge_A => S_precharge_A_NOPS_precharge_A_NOP => S_refresh_1=> S_refresh_2=> S_activate_BS_activate_B => S_activate_B_NOPS_activate_B_NOP => S_read_BS_read_B => S_read_B_NOPS_read_B_NOP => S_gate_BS_gate_B => S_activate_AS_refresh_1 => S_refresh_NOP_11S_refresh_NOP_11 => S_refresh_NOP_12S_refresh_NOP_12 => S_refresh_NOP_13S_refresh_NOP_13 => S_activate_AS_refresh_2 => S_refresh_2S_refresh_NOP_21 => S_refresh_NOP_22S_refresh_NOP_22 => S_refresh_NOP_23S_refresh_NOP_23 => S_activate_ADoes it matter that the 'S_activate_B" branch is longer than "S_refresh_1" or "S_refresh_2" (5 states/cycles vs 4 four the others)Also, last time I wrote a SDRAM controller there were logs of requirements on the powerup sequence - so many clocks before you set the mode word and afterwards.Why is refresh_flag_2 begin set in so many states? it feels wrong... I guess you are trying to issue two refreshes after each rising edge of hsync?

author: Dukov Ahzrukhal
author_rank: Regular Contributor
timestamp: October 14, 2020, 01:47:26 am
content: I am indeed trying to get 2 refresh cycles on every edge of HSYNC. Your comment made me realize that I should be doing the refresh on the falling edge, not the rising edge. I fixed this issue, but it didn't really change anything.

author: hamster_nz
author_rank: Super Contributor
timestamp: October 14, 2020, 03:05:14 am
content: Quote from: Dukov Ahzrukhal on October 14, 2020, 01:47:26 amIs the difference that when Port B is active it takes 11 cycles to get back to S_Activate_A, vs when it is doing a refresh it takes only 10 cycles to get back to S_Activate_A relevant?Also, what clock rate are you running at?For for consistent timing, you really want to latch the SDRAM_DQ in the IO buffers, at the moment it is either latched in a_data_out or b_data_out, which will be implemented your FPGA's fabric, changing timing by a few ns.

author: Dukov Ahzrukhal
author_rank: Regular Contributor
timestamp: October 14, 2020, 03:59:33 am
content: Quote from: hamster_nz on October 14, 2020, 03:05:14 amThe clock rate is 50 MHz for the memory controller, 0.89MHz for the CPU, and 25MHz for the VDG. Timings for the VGD get a little bit complicated, but what is important is that it reads approximately once every 40 cycles of the 50MHz clock. I don't think it matters that when port B is active it takes 11 cycles to get back to S_activate_A. Since the CPU runs at a low clock speed and the VDG does not read data often the timing requirements are not hard to meet.Since the address and RW signals from the CPU and VDG are driven by slower clocks, any single read or write may be executed multiple times by the memory controller. Its not the most elegant design, but I think it should work.I don't fully understand what you said about latching the data in the IO buffers. How do I make that happen? I don't think it matters for this design since its fairly low speed, but I am curious to know anyways as it might help me with future projects.Edit:I got a simulation with the SDRAM model working, and this time the test bench does not include anything but the memory and controller. I'll post the files here in case someone wants to run it.I'll take a close look at it later today and hopefully find the issue.Edit: I fixed it. My mistakes were embarrassingly stupid. Thank you asmi for providing me the link to the memory model.