{"id":2982,"date":"2021-07-29T23:46:10","date_gmt":"2021-07-29T20:46:10","guid":{"rendered":"https:\/\/demensdeum.com\/blog\/?p=2982"},"modified":"2024-12-16T22:32:23","modified_gmt":"2024-12-16T19:32:23","slug":"writing-stuff-in-assembly-for-sega-genesis-3","status":"publish","type":"post","link":"https:\/\/demensdeum.com\/blog\/2021\/07\/29\/writing-stuff-in-assembly-for-sega-genesis-3\/","title":{"rendered":"Writing Assembler for Sega Genesis #3"},"content":{"rendered":"<p>In this note I will describe how to display an image from tiles on the Sega Genesis emulator using assembler.<br \/>The splash image <strong>Demens Deum<\/strong> in the Exodus emulator will look like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2983\" src=\"https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2021\/07\/logo.png\" alt=\"\" width=\"804\" height=\"652\" srcset=\"https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2021\/07\/logo.png 804w, https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2021\/07\/logo-300x243.png 300w, https:\/\/demensdeum.com\/blog\/wp-content\/uploads\/2021\/07\/logo-768x623.png 768w\" sizes=\"auto, (max-width: 804px) 100vw, 804px\" \/><\/p>\n<p>The process of outputting a PNG image using tiles is done step by step:<\/p>\n<ol>\n<li>Reduce image to fit Sega screen<\/li>\n<li>Convert PNG to assembly data code, with separation into colors and tiles<\/li>\n<li>Loading color palette into CRAM<\/li>\n<li>Loading tiles\/patterns into VRAM<\/li>\n<li>Loading tile indices to Plane A\/B addresses into VRAM<\/li>\n<li>You can reduce the image to the size of the Sega screen using your favorite graphics editor, such as Blender.<\/li>\n<\/ol>\n<h3>PNG conversion<\/h3>\n<p>To convert images, you can use the ImaGenesis tool, to work under wine, you need Visual Basic 6 libraries, they can be installed using winetricks (winetricks vb6run), or RICHTX32.OCX can be downloaded from the Internet and placed in the application folder for correct operation.<\/p>\n<p>In ImaGenesis, you need to select 4-bit color, export colors and tiles to two assembler files. Then, in the file with colors, you need to put each color into a word (2 bytes), for this, the opcode dc.w is used.<\/p>\n<p>For example CRAM splash screen:<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>  dc.w $0000 \n  dc.w $0000 \n  dc.w $0222 \n  dc.w $000A \n  dc.w $0226 \n  dc.w $000C \n  dc.w $0220 \n  dc.w $08AA \n  dc.w $0446 \n  dc.w $0EEE \n  dc.w $0244 \n  dc.w $0668 \n  dc.w $0688 \n  dc.w $08AC \n  dc.w $0200 \n  dc.w $0000 \n<\/code><\/pre>\n<\/div>\n<p>Leave the tile file as is, it already contains the correct format for loading. Example of part of the tile file:<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>\tdc.l\t$11111111\t; Tile #0 \n\tdc.l\t$11111111 \n\tdc.l\t$11111111 \n\tdc.l\t$11111111 \n\tdc.l\t$11111111 \n\tdc.l\t$11111111 \n\tdc.l\t$11111111 \n\tdc.l\t$11111111 \n\tdc.l\t$11111111\t; Tile #1 \n\tdc.l\t$11111111 \n\tdc.l\t$11111111 \n\tdc.l\t$11111111 \n\tdc.l\t$11111111 \n\tdc.l\t$11111111 \n\tdc.l\t$11111111 \n\tdc.l\t$11111111 \n<\/code><\/pre>\n<\/div>\n<p>As you can see from the example above, the tiles are an 8&#215;8 grid of CRAM color palette indices.<\/p>\n<h3>Colors in CRAM<\/h3>\n<p>Loading into CRAM is done by setting the color load command at a specific CRAM address in the control port (vdp control). The command format is described in the Sega Genesis Software Manual (1989), I will only add that it is enough to add 0x20000 to the address to move to the next color.<\/p>\n<p>Next, you need to load the color into the data port (vdp data); The easiest way to understand the loading is with the example below:<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>    lea Colors,a0 ; pointer to Colors label \n    move.l #15,d7; colors counter \nVDPCRAMFillLoopStep: \n    move.l  d0,vdp_control_port ;  \n    move.w  (a0)+,d1; \n    move.w  d1,(vdp_data_port); \n    add.l #$20000,d0 ; increment CRAM address \n    dbra d7,VDPCRAMFillLoopStep \n<\/code><\/pre>\n<\/div>\n<h3>Tiles in VRAM<\/h3>\n<p>Next comes loading of tiles\/patterns into the VRAM video memory. To do this, select an address in VRAM, for example 0x00000000. By analogy with CRAM, we address the VDP control port with a command to write to VRAM and the starting address.<\/p>\n<p>After that, you can upload longwords to VRAM, compared to CRAM, you do not need to specify the address for each longword, since there is a VRAM autoincrement mode. You can enable it using the VDP register flag 0x0F (dc.b $02)<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>  lea Tiles,a0 \n  move.l #$40200000,vdp_control_port; write to VRAM command \n  move.w #6136,d0 ; (767 tiles * 8 rows) counter \nTilesVRAMLoop: \n  move.l (a0)+,vdp_data_port; \n  dbra d0,TilesVRAMLoop \n<\/code><\/pre>\n<\/div>\n<h3>Tile indexes in Plane A\/B<\/h3>\n<p>Now we need to fill the screen with tiles by their index. To do this, fill the VRAM at the address Plane A\/B, which is set in the VDP registers (0x02, 0x04). More details about the tricky addressing are in the Sega manual, in my example the VRAM address is 0xC000, we will unload the indices there.<\/p>\n<p>Your image will fill the off-screen VRAM space anyway, so after drawing the screen space, your renderer should stop drawing and continue again when the cursor moves to a new line. There are many options for how to implement this, I used the simplest option of counting on two registers of the image width counter, the cursor position counter.<\/p>\n<p>Code example:<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-unknown\" data-lang=\"unknown\"><code>  move.w #0,d0     ; column index \n  move.w #1,d1     ; tile index \n  move.l #$40000003,(vdp_control_port) ; initial drawing location \n  move.l #2500,d7     ; how many tiles to draw (entire screen ~2500) \n\nimageWidth = 31 \nscreenWidth = 64 \n\nFillBackgroundStep: \n  cmp.w\t#imageWidth,d0 \n  ble.w\tFillBackgroundStepFill \nFillBackgroundStep2: \n  cmp.w\t#imageWidth,d0 \n  bgt.w\tFillBackgroundStepSkip \nFillBackgroundStep3: \n  add #1,d0 \n  cmp.w\t#screenWidth,d0 \n  bge.w\tFillBackgroundStepNewRow \nFillBackgroundStep4: \n  dbra d7,FillBackgroundStep    ; loop to next tile \n\nStuck: \n  nop \n  jmp Stuck \n\nFillBackgroundStepNewRow: \n  move.w #0,d0 \n  jmp FillBackgroundStep4 \nFillBackgroundStepFill: \n  move.w d1,(vdp_data_port)    ; copy the pattern to VPD \n  add #1,d1 \n  jmp FillBackgroundStep2 \nFillBackgroundStepSkip: \n  move.w #0,(vdp_data_port)    ; copy the pattern to VPD \n  jmp FillBackgroundStep3 \n<\/code><\/pre>\n<\/div>\n<p>After that, all that remains is to compile the ROM using vasm, run the simulator, and see the picture.<\/p>\n<h3>Debugging<\/h3>\n<p>Not everything will work out right away, so I want to recommend the following Exodus emulator tools:<\/p>\n<ol>\n<li>m68k processor debugger<\/li>\n<li>Changing the number of m68k processor cycles (for slow-mo mode in the debugger)<\/li>\n<li>Viewers CRAM, VRAM, Plane A\/B<\/li>\n<li>Carefully read the documentation for m68k, the opcodes used (not everything is as obvious as it seems at first glance)<\/li>\n<li>View code\/disassembly examples of games on github<\/li>\n<li>Implement subroutines of processor exceptions, handle them<\/li>\n<\/ol>\n<p>Pointers to subroutines of processor exceptions are placed in the ROM header, also on GitHub there is a project with an interactive runtime debugger for Sega, called genesis-debugger.<\/p>\n<p>Use all the tools available, have fun old school coding and may <strong>Blast Processing<\/strong> be with you!<\/p>\n<h3>Links<\/h3>\n<p><a href=\"https:\/\/gitlab.com\/demensdeum\/segagenesisamples\/-\/tree\/main\/6Image\/vasm\" target=\"_blank\" rel=\"noopener\">https:\/\/gitlab.com\/demensdeum \/segagenesisamples\/-\/tree\/main\/6Image\/vasm<\/a><br \/><a href=\"http:\/\/devster.monkeeh.com\/sega\/imagenesis\/\" target=\"_blank\" rel=\"noopener\">http:\/\/devster.monkeeh.com\/sega\/imagenesis\/<\/a><br \/>\n<a href=\"https:\/\/github.com\/flamewing\/genesis-debugger\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/flamewing\/genesis-debugger<\/a><\/p>\n<h3>Sources<\/h3>\n<p><a href=\"https:\/\/www.chibiakumas.com\/68000\/helloworld.php#LessonH5\" target=\"_blank\" rel=\"noopener\">https:\/\/www.chibiakumas.com\/68000\/helloworld .php#LessonH5<\/a><br \/><a href=\"https:\/\/huguesjohnson.com\/programming\/genesis\/tiles-sprites\/\" target=\"_blank\" rel=\"noopener\">https:\/\/huguesjohnson.com\/programming\/genesis\/tiles-sprites\/<\/a><\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this note I will describe how to display an image from tiles on the Sega Genesis emulator using assembler.The splash image Demens Deum in the Exodus emulator will look like this: The process of outputting a PNG image using tiles is done step by step: Reduce image to fit Sega screen Convert PNG to<a class=\"more-link\" href=\"https:\/\/demensdeum.com\/blog\/2021\/07\/29\/writing-stuff-in-assembly-for-sega-genesis-3\/\">Continue reading <span class=\"screen-reader-text\">&#8220;Writing Assembler for Sega Genesis #3&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[61,52],"tags":[165,172,174],"class_list":["post-2982","post","type-post","status-publish","format-standard","hentry","category-techie","category-tutorials","tag-asm","tag-roms","tag-vasm","entry"],"translation":{"provider":"WPGlobus","version":"3.0.2","language":"en","enabled_languages":["en","ru","zh","de","fr","ja","pt"],"languages":{"en":{"title":true,"content":true,"excerpt":false},"ru":{"title":true,"content":true,"excerpt":false},"zh":{"title":true,"content":true,"excerpt":false},"de":{"title":true,"content":true,"excerpt":false},"fr":{"title":true,"content":true,"excerpt":false},"ja":{"title":true,"content":true,"excerpt":false},"pt":{"title":true,"content":true,"excerpt":false}}},"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/posts\/2982","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/comments?post=2982"}],"version-history":[{"count":11,"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/posts\/2982\/revisions"}],"predecessor-version":[{"id":3896,"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/posts\/2982\/revisions\/3896"}],"wp:attachment":[{"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/media?parent=2982"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/categories?post=2982"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/tags?post=2982"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}