{"id":493,"date":"2013-05-12T10:58:06","date_gmt":"2013-05-12T10:58:06","guid":{"rendered":"http:\/\/retroramblings.net\/?p=493"},"modified":"2013-05-12T18:06:06","modified_gmt":"2013-05-12T18:06:06","slug":"some-linker-script-magic","status":"publish","type":"post","link":"https:\/\/retroramblings.net\/?p=493","title":{"rendered":"Some linker-script magic"},"content":{"rendered":"<p>In my last post I mentioned that I had to employ some ugly hacks in the boot firmware for my ZPU project, to make sure certain structures ended up in SDRAM rather than the initial Boot ROM.<\/p>\n<p>To illustrate the problem let&#8217;s look at a minimal test program:<\/p>\n<pre>short inconvenience;\r\n\r\nint main(int argc,char **argv)\r\n{\r\n\u00a0\u00a0 \u00a0inconvenience=0x0123;\r\n\u00a0\u00a0 \u00a0return(0);\r\n}<\/pre>\n<p>This little program declares a 16-bit word global variable, and then writes to it.\u00a0 The assembly output produced by<\/p>\n<pre>zpu-elf-gcc -Os -S bsstest.c<\/pre>\n<p>is as follows:<\/p>\n<pre>\u00a0\u00a0\u00a0 .file\u00a0\u00a0 \u00a0\"bsstest.c\"\r\n.text\r\n\u00a0\u00a0 \u00a0.globl\u00a0\u00a0 \u00a0main\r\n\u00a0\u00a0 \u00a0.type\u00a0\u00a0 \u00a0main, @function\r\nmain:\r\n\u00a0\u00a0 \u00a0im 291\r\n\u00a0\u00a0 \u00a0nop\r\n\u00a0\u00a0 \u00a0im inconvenience\r\n\u00a0\u00a0 \u00a0storeh\r\n\u00a0\u00a0 \u00a0im 0\r\n\u00a0\u00a0 \u00a0nop\r\n\u00a0\u00a0 \u00a0im _memreg+0\r\n\u00a0\u00a0 \u00a0store\r\n\u00a0\u00a0 \u00a0poppc\r\n\u00a0\u00a0 \u00a0.size\u00a0\u00a0 \u00a0main, .-main\r\n\u00a0\u00a0 \u00a0.comm\u00a0\u00a0 \u00a0inconvenience,2,4\r\n\u00a0\u00a0 \u00a0.ident\u00a0\u00a0 \u00a0\"GCC: (GNU) 3.4.2\"<\/pre>\n<p>Note the storeh instruction half way down.\u00a0 That&#8217;s the source of my problem.\u00a0 I&#8217;ve implemented storeh in hardware for SDRAM, but not for the BlockRAM-based Boot code, and I&#8217;d really like to avoid doing the latter if possible, because doing a 16-bit write to a 32-bit wide RAM is going to be messy and eat up logic elements.\u00a0 The boot code is also rather on the large side, so it would be nice to avoid storing unitialised data in there at all if possible.<br \/>\n<!--more--><br \/>\nThe quick solution is simply to make the variable an int (32-bits) instead of a short (16-bits) &#8211; but that doesn&#8217;t help if what&#8217;s declared is a structure that has critical alignment requirements, as is the case for the FAT filesystem structures that the boot code must deal with.\u00a0 The solution I used for my last post was instead of declaring a struct, to declare a pointer to a struct, and initialise that pointer to some random location in SDRAM.\u00a0 That works just fine when there are just one or two such structs, but it&#8217;s ugly and the chances of bugs creeping in as structs collide increase with the program&#8217;s complexity, so a better solution is needed.<\/p>\n<p>The solution to this problem is to employ a linker script, which we supply to gcc with the -T option.<\/p>\n<p>Before diving into the linker script, let&#8217;s take a quick look at my ZPUTest project&#8217;s address decoding:<\/p>\n<pre>case mem_addr(31 downto 16) is\r\n\twhen X\"0000\" =&gt;\t-- Boot BlockRAM\r\n\t\t...\r\n\twhen X\"FFFE\" =&gt;\t-- VGA controller\r\n\t\t...\r\n\twhen X\"FFFF\" =&gt;\t-- Peripherals\r\n\t\t...\r\n\twhen others =&gt; -- SDRAM access\r\n\t\t...<\/pre>\n<p>So the memory map looks like this:<\/p>\n<ul>\n<li>0x0000 &#8211; 0xFFFF\u00a0 &#8211;\u00a0 Boot code in Block RAM, aliased to fill the 64k region.<\/li>\n<li>0xFFFE0000 &#8211; 0xFFFEFFFF\u00a0 &#8211;\u00a0 VGA controller registers<\/li>\n<li>\u00a00xFFFF0000 &#8211; 0xFFFFFFFF\u00a0 &#8211;\u00a0 Peripheral registers<\/li>\n<li>All other addresses\u00a0 &#8211;\u00a0 SDRAM\u00a0 (Actual range depends on the target board&#8217;s SDRAM chips).<\/li>\n<li>(The stack s also memory-mapped within my variant of the ZPU core itself, to 0x40000000)<\/li>\n<\/ul>\n<p>So our goal with the linker script is to ensure anything we want to end up in SDRAM has an address greater than 0x10000.<\/p>\n<p>We&#8217;ll start the linker script by defining some memory sections:<\/p>\n<pre>MEMORY\r\n{\r\n    BOOT (rx) : ORIGIN = 0x00000000, LENGTH = 0x00002000 \/* 8192 bytes *\/\r\n    SDRAM (rw)\u00a0 : ORIGIN = 0x007ff000, LENGTH = 0x00001000 \/* 4k at top end of DE1's SDRAM *\/\r\n    STACK (rw) : ORIGIN = 0x40000000, LENGTH = 0x00000400 \/* 1024 bytes *\/\r\n}<\/pre>\n<p>The BOOT section is the BlockRAM at the beginning of the memory map. At the time of writing it&#8217;s currently 8KB (16 M4Ks) in size, but I&#8217;m hoping to get that down to 4KB in time. Once nice effect of defining memory regions like this is that we get a compile error if the code doesn&#8217;t fit.<\/p>\n<p>The SDRAM section is set here to point to a small region at the end of the DE1 board&#8217;s 8 megabytes of SDRAM. It doesn&#8217;t really matter where this falls, but since the whole point of this project was a minimal core for loading files into RAM from SD Card, it needs to be somewhere that won&#8217;t be overwritten when we load files!<\/p>\n<pre>SECTIONS\r\n{\r\n  \/* first section is .fixed_vectors which is used for startup code *\/\r\n  . = 0x0000000;\r\n  .fixed_vectors :\r\n  {\r\n    *(.fixed_vectors)\r\n  }&gt;BOOT\r\n<\/pre>\n<p>The .fixed_vectors section comes from the crt0.s startup code, and must be the first section in the BOOT region.  It contains the initial jump instruction, a few memory-based registers for GCC (which isn&#8217;t happy building code for CPUs that don&#8217;t have any traditional registers at all) and the emulation code for instructions that aren&#8217;t implemented in hardware.<\/p>\n<pre>\r\n  \/* Remaining code sections *\/\r\n  . = ALIGN(4);\r\n  .text :\r\n  {\r\n    *(.text)                   \/* remaining code *\/\r\n  } &gt;BOOT\r\n\r\n  \/* .rodata section which is used for read-only data (constants) *\/\r\n  . = ALIGN(4);\r\n  .rodata :\r\n  {\r\n    *(.rodata)\r\n  } &gt;BOOT\r\n<\/pre>\n<p>The above adds the rest of the program code and any constants and strings to the Boot ROM.<\/p>\n<pre>\r\n  \/* .data section which is used for initialized data *\/\r\n  . = ALIGN(4);\r\n  .data :\r\n  {\r\n    _data = . ;\r\n    *(.data)\r\n    SORT(CONSTRUCTORS)\r\n    . = ALIGN(4);\r\n  } &gt;BOOT\r\n  . = ALIGN(4);\r\n  _romend = . ;\r\n<\/pre>\n<p>Initialised data is the only problem we haven&#8217;t addressed.  This will still end up in the Boot ROM.  The implication of this is that declaring a global variable &#8220;short val;&#8221; should now be fine, but &#8220;short val=0x1234;&#8221; will still cause problems.<br \/>\nThe _romend symbol isn&#8217;t needed, but it&#8217;s convenient to include because we can use it to keep an eye on code size.<\/p>\n<pre>\r\n  \/* .bss section which is used for uninitialized data *\/\r\n  .bss :\r\n  {\r\n    __bss_start = . ;\r\n    __bss_start__ = . ;\r\n    *(.bss)\r\n    *(COMMON)\r\n  } &gt;SDRAM\r\n  __bss_end__ = . ;\r\n}<\/pre>\n<p>Finally, the BSS section, which will now end up in SDRAM.  This also has the nice side-effect of reducing the space needed for the Boot ROM, since we no longer need to allow room for the BSS data.<br \/>\nAgain, the __bss_start__ and __bss_end__ symbols aren&#8217;t strictly necessary, but they allow us to keep tabs on locations and sizes.<br \/>\nI do this with an extra target in the makefile, like so:<\/p>\n<pre>\r\n%.rpt: %.elf\r\n\t$(DUMP) -x $< | grep _romend   #  End of Boot ROM\r\n\t$(DUMP) -x $< | grep __bss_start__   #  Start of BSS data in SDRAM\r\n\t$(DUMP) -x $< | grep __bss_end__   #  End of BSS data in SDRAM\r\n<\/pre>\n<p>Every build finishes by printing this to the shell:<\/p>\n<pre>\r\nzpu-elf-objdump -x boot.elf | grep _romend   #  End of Boot ROM\r\n000018ec g       *ABS*\t00000000 _romend\r\nzpu-elf-objdump -x boot.elf | grep __bss_start__   #  Start of BSS data in SDRAM\r\n007ff000 g       .bss\t00000000 __bss_start__\r\nzpu-elf-objdump -x boot.elf | grep __bss_end__   #  End of BSS data in SDRAM\r\n007ff2f8 g       *ABS*\t00000000 __bss_end__\r\n<\/pre>\n<p>The Boot ROM currently ends at 0x18ec, while the BSS data occupies SDRAM between 0x7ff000 and 0x7ff2f8<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In my last post I mentioned that I had to employ some ugly hacks in the boot firmware for my ZPU project, to make sure certain structures ended up in SDRAM rather than the initial Boot ROM. To illustrate the &hellip; <a href=\"https:\/\/retroramblings.net\/?p=493\">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":[1],"tags":[],"class_list":["post-493","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts\/493","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=493"}],"version-history":[{"count":8,"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts\/493\/revisions"}],"predecessor-version":[{"id":501,"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts\/493\/revisions\/501"}],"wp:attachment":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=493"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=493"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=493"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}