{"id":1053,"date":"2015-01-21T20:20:44","date_gmt":"2015-01-21T20:20:44","guid":{"rendered":"http:\/\/retroramblings.net\/?p=1053"},"modified":"2015-01-21T20:23:01","modified_gmt":"2015-01-21T20:23:01","slug":"a-closer-look-at-the-osdcontrol-module-7","status":"publish","type":"post","link":"https:\/\/retroramblings.net\/?p=1053","title":{"rendered":"A closer look at the OSD\/Control Module"},"content":{"rendered":"<p><strong>Part 7 &#8211; Loading data from SD card.<\/strong><\/p>\n<p>In this part of the series I&#8217;m going to look at the most useful aspect of the control module &#8211; using it load data from SD card and pass it to the host core.<\/p>\n<p>To make a meaningful demonstration, the host core needed to be able to do something with the received data, so I&#8217;ve pulled in the SDRAM controller and VGA framebuffer from the ZPUDemos project.\u00a0 What I&#8217;ve called the &#8220;host core&#8221;, the part of the project which the ZPU-based control module is supporting, is now capable of displaying a 640x480x16-bit VGA screen from SDRAM, and as such the project is now quite a bit more complicated; however, the only new file needed by the control module itself is spi.vhd which handles communication with the SD card.<br \/>\n<a href=\"http:\/\/retroramblings.net\/wp-content\/uploads\/2015\/01\/Fileselector.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/retroramblings.net\/wp-content\/uploads\/2015\/01\/Fileselector.jpg\" alt=\"Fileselector\" width=\"752\" height=\"500\" class=\"alignnone size-full wp-image-1061\" srcset=\"https:\/\/retroramblings.net\/wp-content\/uploads\/2015\/01\/Fileselector.jpg 752w, https:\/\/retroramblings.net\/wp-content\/uploads\/2015\/01\/Fileselector-300x199.jpg 300w, https:\/\/retroramblings.net\/wp-content\/uploads\/2015\/01\/Fileselector-451x300.jpg 451w\" sizes=\"auto, (max-width: 752px) 100vw, 752px\" \/><\/a><!--more--><\/p>\n<p>So let&#8217;s take a look at the SD card communication.\u00a0 SD cards can operate in one of two modes; one is a proprietory system specific to SD cards which, until fairly recently was the subject of a certain amount of secrecy, while the other is a simple microcontroller-compatible SPI protocol, which is what we&#8217;ll use in the control module.<\/p>\n<p>I won&#8217;t cover the protocol in detail here, but the C source files CtrlModule\/Firmware\/spi.[c|h] handle the actual block-level transfer of data between FPGA and SD card.\u00a0 They do this by writing to two new hardware registers at 0xFFFFFFD0 and 0xFFFFFFD4.\u00a0 The first of these controls the card select signal, and also selects between fast and slow transfer speeds (SD cards must be initialised at a slow speed, but can then be driven faster.)\u00a0 The second register is used to transfer data.<\/p>\n<p>The actual hardware module looks like this (derived from the SPI interface from the TG68-based Minimig variants):<\/p>\n<pre>entity spi_interface is\r\n\tport (\r\n\t\tsysclk : in std_logic;\r\n\t\treset : in std_logic;\r\n\r\n\t\t-- Host interface\r\n\t\tspiclk_in : in std_logic;\t-- Momentary high pulse\r\n\t\thost_to_spi : in std_logic_vector(7 downto 0);\r\n\t\tspi_to_host : out std_logic_vector(7 downto 0);\r\n\t\ttrigger : in std_logic;  -- Momentary high pulse\r\n\t\tbusy : buffer std_logic;\r\n\r\n\t\t-- Hardware interface\r\n\t\tmiso : in std_logic;\r\n\t\tmosi : out std_logic;\r\n\t\tspiclk_out : out std_logic -- 50% duty cycle\r\n\t);\r\nend entity;\r\n\r\narchitecture rtl of spi_interface is\r\nsignal sck : std_logic;\r\nsignal sd_shift : std_logic_vector(7 downto 0);\r\nsignal shiftcnt : std_logic_vector(3 downto 0);\r\nbegin\r\n\r\n-----------------------------------------------------------------\r\n-- SPI-Interface\r\n-----------------------------------------------------------------\t\r\n\tspiclk_out &lt;= sck;\r\n\tbusy &lt;= (not shiftcnt(3)) or trigger; -- Or-ing in the trigger signal makes the busy signal respond immediately\r\n   spi_to_host &lt;= sd_shift;\r\n\r\n\tPROCESS (sysclk, reset) BEGIN\r\n\r\n\t\tIF reset ='0' THEN \r\n\t\t\tshiftcnt(3)&lt;='0';\r\n\t\t\tsck &lt;= '0';\r\n\t\tELSIF rising_edge(sysclk) then\r\n\t\t\tIF trigger='1' then\r\n\t\t\t\tshiftcnt &lt;= \"0111\";  -- shift out 8 bits, underflow will set bit 3, mapped to busy\r\n\t\t\t\tsd_shift &lt;= host_to_spi(7 downto 0);\r\n\t\t\t\tsck &lt;= '1';\r\n\t\t\tELSE\r\n\t\t\t\tIF spiclk_in='1' and busy='1' THEN\r\n\t\t\t\t\tIF sck='1' THEN\r\n\t\t\t\t\t\tmosi&lt;=sd_shift(7);\r\n\t\t\t\t\t\tsck &lt;='0';\r\n\t\t\t\t\tELSE\t\r\n\t\t\t\t\t\tsck &lt;='1';\r\n\t\t\t\t\t\tsd_shift &lt;= sd_shift(6 downto 0)&amp;miso;\r\n\t\t\t\t\t\tshiftcnt &lt;= shiftcnt-1;\r\n\t\t\t\t\tEND IF;\r\n\t\t\t\tEND IF;\r\n\t\t\tEND IF;\r\n\t\tend if;\r\n\tEND PROCESS;\r\n\r\nend architecture;\r\n<\/pre>\n<p>We need to supply an spiclock_in signal for the SD card in the form of a momentary pulse. This should be no greater than 400KHz in slow mode, and somewhat faster in fast mode&#8230;<\/p>\n<pre>\r\n-- SPI Clock counter\r\nsignal spi_tick : unsigned(8 downto 0);\r\nsignal spiclk_in : std_logic;\r\nsignal spi_fast : std_logic;\r\n...\r\n-- SPI Timer\r\nprocess(clk)\r\nbegin\r\n\tif rising_edge(clk) then\r\n\t\tspiclk_in<='0';\r\n\t\tspi_tick<=spi_tick+1;\r\n\t\tif (spi_fast='1' and spi_tick(5)='1') or spi_tick(8)='1' then\r\n\t\t\tspiclk_in<='1'; -- Momentary pulse for SPI host.\r\n\t\t\tspi_tick<='0'&#038;X\"00\";\r\n\t\tend if;\r\n\tend if;\r\nend process;\r\n<\/pre>\n<p>Then the SPI peripheral is accessed in software via the two new registers, like so:<\/p>\n<pre>\r\n\t-- Writes:\r\n\twhen X\"D0\" => -- SPI CS\r\n\t\tspi_cs<=not mem_write(0);\r\n\t\tspi_fast<=mem_write(8);\r\n\t\tmem_busy<='0';\r\n\r\n\twhen X\"D4\" => -- SPI Data (blocking)\r\n\t\tspi_trigger<='1';\r\n\t\thost_to_spi<=mem_write(7 downto 0);\r\n\t\tspi_active<='1';\r\n...\r\n\t-- Reads:\r\n\twhen X\"D0\" => -- SPI Status\r\n\t\tmem_read<=(others=>'X');\r\n\t\tmem_read(15)<=spi_busy;\r\n\t\tmem_busy<='0';\r\n\r\n\twhen X\"D4\" => -- SPI read (blocking)\r\n\t\tspi_active<='1';\r\n<\/pre>\n<p>Notice that accesses to register D0 terminate the cycle by setting mem_busy to '0', but accesses to D4 don't.  Instead accesses to this register block the CPU until the request has been processed; the CPU is subsequently released, like so:<\/p>\n<pre>\r\n\t-- SPI cycle termination\r\n\tif spi_active='1' and spi_busy='0' then\r\n\t\tmem_read(7 downto 0)<=spi_to_host;\r\n\t\tmem_read(31 downto 8)<=(others => '0');\r\n\t\tspi_active<='0';\r\n\t\tmem_busy<='0';\r\n\tend if;\r\n<\/pre>\n<p>Block level access to the card is only half the story, however - we need to be able to access individual files and directories in the filesystem.\u00a0 This is handled by CtrlModule\/Firmware\/minfat.c, which is a minimal FAT32 filesystem implementation, again loosely based on similar code from the Minimig project but cut down and simplified to take up as little ROM space as possible.<\/p>\n<p>Again, I won't go into too much detail on using the minfat.c code - the firmware's source provides a reference; suffice it to say that loading a specific file from the SD card looks something like this:<\/p>\n<pre>\r\nfileTYPE file; \/\/ Declare as a global\r\n\r\n...\r\n\r\nif(!FindDrive()) \/\/ Initialise the SD card, find the first partition (if applicable) and find the filesystem.\r\n\treturn(0);\r\n\r\nif((opened=FileOpen(&file,filename)))\r\n{\r\n\tint filesize=file.size;\r\n\twhile(filesize>0)\r\n\t{\r\n\t\tif(FileRead(&file,sector_buffer))\r\n\t\t{\r\n\t\t\tFileNextSector(&file);\r\n\t\t\t\/\/ Do something with the data which can be found in sector_buffer...\r\n\t\t\tfilesize-=512;\r\n\t\t}\r\n\t}\r\n}\r\n<\/pre>\n<p>What the project does with the data depends very much on the underlying core.  In the OneChipMSX project the underlying core uses a Z80 program to read data from an EPCS4 configuration device, and writes that data to SDRAM, so I replaced that Z80 program with a simpler version and made the control module supply the data byte-by-byte, similar to how the EPCS4 does it.<br \/>\nThe PC-Engine core used a state machine to load data from a Flash chip, and again copied it to SDRAM, so I simply replaced the state machine with a version which received the data from the control module instead of the flash chip.<br \/>\nThis demo simply passes the data to the Host core via a 32-bit data channel, and with req \/ ack handshaking.  The host core writes the received data to SDRAM at the framebuffer address, so the VGA controller will display the contents of whichever file you select.<\/p>\n<p>The final problem to be solved is displaying a file selector.  I ended up using the existing menu infrastructure for the file selector, declaring an empty menu, like so:<\/p>\n<pre>\r\nstatic char romfilenames[13][30];\r\n\r\nstatic struct menu_entry rommenu[]=\r\n{\r\n\t{MENU_ENTRY_CALLBACK,romfilenames[0],MENU_ACTION(&selectrom)},\r\n\t{MENU_ENTRY_CALLBACK,romfilenames[1],MENU_ACTION(&selectrom)},\r\n\t{MENU_ENTRY_CALLBACK,romfilenames[2],MENU_ACTION(&selectrom)},\r\n\t{MENU_ENTRY_CALLBACK,romfilenames[3],MENU_ACTION(&selectrom)},\r\n\t{MENU_ENTRY_CALLBACK,romfilenames[4],MENU_ACTION(&selectrom)},\r\n\t{MENU_ENTRY_CALLBACK,romfilenames[5],MENU_ACTION(&selectrom)},\r\n\t{MENU_ENTRY_CALLBACK,romfilenames[6],MENU_ACTION(&selectrom)},\r\n\t{MENU_ENTRY_CALLBACK,romfilenames[7],MENU_ACTION(&selectrom)},\r\n\t{MENU_ENTRY_CALLBACK,romfilenames[8],MENU_ACTION(&selectrom)},\r\n\t{MENU_ENTRY_CALLBACK,romfilenames[9],MENU_ACTION(&selectrom)},\r\n\t{MENU_ENTRY_CALLBACK,romfilenames[10],MENU_ACTION(&selectrom)},\r\n\t{MENU_ENTRY_CALLBACK,romfilenames[11],MENU_ACTION(&selectrom)},\r\n\t{MENU_ENTRY_CALLBACK,romfilenames[12],MENU_ACTION(&selectrom)},\r\n\t{MENU_ENTRY_SUBMENU,\"Back\",MENU_ACTION(0)},\r\n\t{MENU_ENTRY_NULL,0,MENU_ACTION(scrollroms)}\r\n};\r\n<\/pre>\n<p>While this eats up a bit more RAM in the program \"ROM\" than I would have liked, it does avoid the need for too much special case code, and helps keep the code size down.   As a bit of an ad-hoc hack, I added to the menu code so that if the cursor keys or page up \/ down keys were pressed while the cursor was already at the top or bottom of the menu, a callback function will be called to redraw the menu - this allows me to handle scrolling, like so:<\/p>\n<pre>\r\nstatic void scrollroms(int row)\r\n{\r\n\tswitch(row)\r\n\t{\r\n\t\tcase ROW_LINEUP:\r\n\t\t\tif(romindex)\r\n\t\t\t\t--romindex;\r\n\t\t\tbreak;\r\n\t\tcase ROW_PAGEUP:\r\n\t\t\tromindex-=16;\r\n\t\t\tif(romindex<0)\r\n\t\t\t\tromindex=0;\r\n\t\t\tbreak;\r\n\t\tcase ROW_LINEDOWN:\r\n\t\t\t++romindex;\r\n\t\t\tbreak;\r\n\t\tcase ROW_PAGEDOWN:\r\n\t\t\tromindex+=16;\r\n\t\t\tbreak;\r\n\t}\r\n\tlistroms();\r\n\tMenu_Draw();\r\n}\r\n<\/pre>\n<p>The code in minfat.c has provision for reading a directory, though in the interests of simplicity and small code, it only supports reading directory entries in sequential order.  Populating the file selector menu looks like this:<\/p>\n<pre>\r\nstatic void listroms()\r\n{\r\n\tint i,j;\r\n\tj=0;\r\n\tfor(i=0;(j&lt;romindex) && (i&lt;dir_entries);++i)\r\n\t{\r\n\t\tDIRENTRY *p=NextDirEntry(i);\r\n\t\tif(p)\r\n\t\t\t++j;\r\n\t}\r\n\r\n\tfor(j=0;(j&lt;12) && (i&lt;dir_entries);++i)\r\n\t{\r\n\t\tDIRENTRY *p=NextDirEntry(i);\r\n\t\tif(p)\r\n\t\t{\r\n\t\t\tif(p-&gt;Attributes&ATTR_DIRECTORY)\r\n\t\t\t{\r\n\t\t\t\trommenu[j].action=MENU_ACTION(&selectdir);\r\n\t\t\t\tromfilenames[j][0]=16; \/\/ Right arrow\r\n\t\t\t\tromfilenames[j][1]=' ';\r\n\t\t\t\tif(longfilename[0])\r\n\t\t\t\t\tcopyname(romfilenames[j++]+2,longfilename,28);\r\n\t\t\t\telse\r\n\t\t\t\t\tcopyname(romfilenames[j++]+2,p-&gt;Name,11);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\trommenu[j].action=MENU_ACTION(&selectrom);\r\n\t\t\t\tif(longfilename[0])\r\n\t\t\t\t\tcopyname(romfilenames[j++],longfilename,28);\r\n\t\t\t\telse\r\n\t\t\t\t\tcopyname(romfilenames[j++],p-&gt;Name,11);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t\tromfilenames[j][0]=0;\r\n\t}\r\n\tfor(;j&lt;12;++j)\r\n\t\tromfilenames[j][0]=0;\r\n}\r\n<\/pre>\n<p>The <a href=\"https:\/\/github.com\/robinsonb5\/CtrlModuleTutorial\">Github repo<\/a> contains full source for the demo (Tagged as Step6), and also contains four raw 640x480x16 image files which should be copied to an SD card.  The demo can load and display these files, and the menu options can be used to mess with the colours.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Part 7 &#8211; Loading data from SD card. In this part of the series I&#8217;m going to look at the most useful aspect of the control module &#8211; using it load data from SD card and pass it to the &hellip; <a href=\"https:\/\/retroramblings.net\/?p=1053\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,8],"tags":[],"class_list":["post-1053","post","type-post","status-publish","format-standard","hentry","category-fpga","category-hardware"],"_links":{"self":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts\/1053","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1053"}],"version-history":[{"count":12,"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts\/1053\/revisions"}],"predecessor-version":[{"id":1067,"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts\/1053\/revisions\/1067"}],"wp:attachment":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1053"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1053"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1053"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}