Jump to content

Search the Community

Showing results for tags 'mac/65'.

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


Forums

  • Atari Systems
    • Atari General
    • Atari 2600
    • Atari 5200
    • Atari 7800
    • Atari Lynx
    • Atari Jaguar
    • Atari VCS
    • Dedicated Systems
    • Atari 8-Bit Computers
    • Atari ST/TT/Falcon Computers
    • Atari Portfolio
  • Classic Consoles
    • Classic Console Discussion
    • ColecoVision / Adam
    • Intellivision / Aquarius
    • Bally Arcade/Astrocade
    • Odyssey 2 / Videopac
    • Vectrex
    • Nintendo Entertainment System (NES) / Famicom
    • Super Nintendo Entertainment System (SNES) / Super Famicom
    • Sega Genesis
    • 3DO Interactive Multiplayer
    • Dreamcast
    • SMS High Score Club
    • TG-16/PC Engine High Score Club
  • Classic Computing
    • Classic Computing Discussion
    • Apple II Computers
    • TI-99/4A Computers
    • Commodore 8-bit Computers
    • Commodore Amiga
    • Tandy Computers
  • Modern Consoles
    • Modern Gaming Discussion
    • Sony Playstation 5
    • Xbox Series S/X
    • Atari VCS (Redirect)
    • Nintendo Switch
    • Microsoft Xbox One
    • Sony PlayStation 4
    • Microsoft Xbox 360
    • Sony Playstation 3
    • Nintendo Wii / Wii U
  • Gaming General
    • Gaming General Discussion
    • Arcade and Pinball
    • Emulation
    • Hardware
    • Prototypes
    • Gaming Publications and Websites
    • International
  • Marketplace
    • Buy, Sell, and Trade
    • Auction Central
    • Wanted
    • Free Games and More
    • User Feedback Forum
  • Community
  • Community
    • Events
    • Show Us Your Collection!
    • Member Blogs
    • High Score Clubs
    • Poll Forum
    • Contests
    • User Groups
    • AtariAge News Discussion
    • User Submitted News
  • Game Programming
    • Homebrew Discussion
    • Programming
    • Hacks
  • Site
    • Announcements
    • Forum Questions and Answers
    • AtariAge Store Discussion
    • Site and Forum Feedback
    • Rarity Guide
    • Archived Forums
  • PC Gaming
  • The Club of Clubs's Discussion
  • I Hate Sauron's Topics
  • 1088 XEL/XLD Owners and Builders's Topics
  • Atari BBS Gurus's Community Chat
  • Atari BBS Gurus's BBS Callers
  • Atari BBS Gurus's BBS SysOps
  • Atari BBS Gurus's Resources
  • Atari Lynx Programmer Club's CC65
  • Atari Lynx Programmer Club's ASM
  • Atari Lynx Programmer Club's Lynx Programming
  • Atari Lynx Programmer Club's Music/Sound
  • Atari Lynx Programmer Club's Graphics
  • The Official AtariAge Shitpost Club's Shitty meme repository
  • The Official AtariAge Shitpost Club's Read this before you enter too deep
  • Arcade Gaming's Discussion
  • Tesla's Vehicles
  • Tesla's Solar
  • Tesla's PowerWall
  • Tesla's General
  • Harmony/Melody's General
  • Harmony/Melody's CDFJ
  • Harmony/Melody's DPC+
  • Harmony/Melody's BUS
  • Harmony/Melody's CDFJ+
  • ZeroPage Homebrew's Discussion
  • Furry Club's Chat/RP
  • PSPMinis.com's General PSP Minis Discussion and Questions
  • PSPMinis.com's Reviews
  • Atari Lynx 30th Birthday's 30th Birthday Programming Competition Games
  • 3D Printing Club's Chat
  • Drivers' Club's Members' Vehicles
  • Drivers' Club's Drives & Events
  • Drivers' Club's Wrenching
  • Drivers' Club's Found in the Wild
  • Drivers' Club's General Discussion
  • Dirtarians's Members' Rigs
  • Dirtarians's Trail Runs & Reports
  • Dirtarians's Wrenching
  • Dirtarians's General Discussion
  • The Green Herb's Discussions
  • Robin Gravel's new blog's My blog
  • Robin Gravel's new blog's Games released
  • Robin Gravel's new blog's The Flintstones Comic Strip
  • Atari Video Club's Harmony Games
  • Atari Video Club's The Atari Gamer
  • Atari Video Club's Video Game Summit
  • Atari Video Club's Discsuuions
  • Atari Video Club's Concerto Games
  • Atari Video Club's AVC Games
  • Star Wars - The Original Trilogy's Star Wars Talk
  • PlusCart User's Bug reports
  • PlusCart User's Discussion
  • DMGD Club's Incoming!
  • DASM's General
  • AtariVox's Topics
  • Gran Turismo's Gran Turismo
  • Gran Turismo's Misc.
  • Gran Turismo's Announcements
  • The Food Club's Food
  • The Food Club's Drinks
  • The Food Club's Read me first!
  • The (Not So) Official Arcade Archives Club's Rules (READ FIRST)
  • The (Not So) Official Arcade Archives Club's Feedback
  • The (Not So) Official Arcade Archives Club's Rumor Mill
  • The (Not So) Official Arcade Archives Club's Coming Soon
  • The (Not So) Official Arcade Archives Club's General Talk
  • The (Not So) Official Arcade Archives Club's High Score Arena
  • Adelaide South Australia Atari Chat's General Chat & Welcome
  • Adelaide South Australia Atari Chat's Meets
  • Adelaide South Australia Atari Chat's Trades & Swaps
  • KC-ACE Reboot's KC-ACE Reboot Forum
  • The Official Lost Gaming Club's Lost Gaming
  • The Official Lost Gaming Club's Undumped Games
  • The Official Lost Gaming Club's Tip Of My Tounge
  • The Official Lost Gaming Club's Lost Gaming Vault
  • The Official Lost Gaming Club's Club Info
  • GIMP Users's Discussion
  • The Homebrew Discussion's Topics
  • Hair Club for Men's Bald? BEGONE!
  • Alternate Reality's Topics
  • Board games, card and figure games's Topics
  • please delete's Topics
  • StellaRT's Topics

Blogs

  • BinaryGoddess' Blog
  • Albert's Blog
  • MegaManFan's Blog
  • Ed Siegler's Blog
  • FireTiger's Blog
  • Atari Rescue Group's Blog
  • EricBall's Tech Projects
  • liquid_sky's Blog
  • Cybernoid's Blog
  • Lost Blog
  • shep's Blog
  • Trey's Blog
  • Boo
  • Kepone's Blog
  • Beware of Kiwi
  • Fun in the beer mines
  • PacManPlus' Blog
  • Atari 8-bit Moria port
  • Tim's Blog
  • Mindfield's Chewy-Centered Blog
  • The Long Dark Teatime of the Soul
  • TP's Blog
  • Adam Sessler's Brutally Honest Blog
  • Shut Up and Play Yer Atari
  • None
  • Atarinvader's Blog
  • Atari 8-bit archiving
  • Brunobits' Blog
  • ATARIeric's Blog
  • wrenchien's Blog
  • Trade-N-Games' Blog
  • wapchimp's Blog
  • Shared Words
  • Bastard's Blog
  • homerwannabee's Blog
  • Haydn Jones' Blog
  • The World According To Yuppicide
  • How I did It
  • Buck's Blog
  • atwwong's Blog
  • 1
  • sandmountainslim's Blog
  • Atari Jaguar Projects + More
  • StanJr's Blog
  • Schmutzpuppe's Blog
  • Bullitt's Blog
  • panda_racer's Blog
  • Inky's Blog
  • Lauren's Place
  • DanBoris' Tech Blog
  • atariauctions' Blog
  • Planet Bob
  • CSIXTY4.com
  • Robin Gravel's Blog
  • lestergame
  • Duke 4ever's Blog
  • Atari Haiku Blog
  • An7ron
  • glitch's Blog
  • Coleco-Atari Era
  • Kenfused's Blog
  • Ralph3's Blog
  • nester's one star gaming
  • Halt and Catch Fire
  • lizard's Blog
  • Laner's Classic Gaming Blog
  • Page 6
  • keilbaca's rants
  • SirWilliam's Blog
  • Birdie3's blog
  • MattG/Snyper2099's Blog
  • madmjennifer's Blog
  • Ablogalypse Now
  • Endless Quest
  • Greenious' Blog
  • wookie's Blog
  • Justclaws' Blog
  • VTAtari's Blog
  • SID CROWE TESTING THE blog softwareeee
  • Dutchman2000's Blog
  • Famicoman's Blog
  • scogey's Blog
  • Retro Gaming Obscuria
  • atarifan49's Blog
  • Chronogamer
  • flavoredthunder's Blog
  • Shernand's Blog
  • Robert M's Blog
  • albaki's Blog
  • BTHOTU's Blog
  • Zach's Projects
  • BuzzTron-451's Blog
  • The Occasional Coder
  • Joystick Lunatic Software on AtariAge
  • Zander's Blog
  • The randomness that is Mr. 8-bit/16-bit.
  • bluetriforce's Blog
  • ubikuberalles' Blog
  • Worm Development Blog
  • Eight Bit's Blog
  • mos6507's Blog
  • phaxda's Blog
  • potatohead's Blog
  • Mountain King's Blog
  • The Southsider
  • The World is Flat?
  • brianwolters' Blog
  • Bidouille's Blog
  • Zybex/Atariware Blog
  • JagDiesel's Palace 2
  • Sega_master's Blog
  • Deep into the Mind Game
  • Bob's Blog
  • Rockin' Kat's Blog
  • Push Me, Pullman
  • (Insert stupid Blog name here)
  • dgob123's INTV Blog
  • Random Terrain's Tetraternarium
  • Odyssey Development Corner
  • Pacmaniax
  • GPD Comics Blog
  • sergiomario's Blog
  • prorobb's Blog
  • Days Atari Events
  • gamester1's Blog
  • Shannon's Blog
  • Mord's Blog
  • liquidcross.com - blog
  • MIPS HEAVY INDUSTRIES
  • MayDay Today
  • javiero's Blog
  • Great Exploitations
  • Monster Angriff's Blog
  • Draikar's Blog
  • Random Acts of Randomness
  • TROGBlog
  • hex65000's Blog
  • Being Of The Importance Of Shallow Musing.
  • daclmi's Blog
  • 2600 in 2006
  • Sayton's Blog
  • For whom it may concern
  • Osbo's Blog
  • ataridude81's Blog
  • Wiesbaden Gaming Lab
  • SpiceWare's Blog
  • The Upward Spiral
  • Web-Frickin'-Log
  • Starosti 8bitového grafika
  • WWW.BUYATARI.TK
  • commodore & atari :)'s Blog
  • Dusk2600's Blog
  • GAMEBOT
  • Lynx 20 years
  • Songbird Productions
  • SpaceInvader's Blog
  • Retro point of view
  • VampyricDreams666's Blog
  • le geek's nonsense
  • Hardcore's Nostalgia
  • 4old-times-sake's Blog
  • shadow460's Blog
  • AtariJr's Blog
  • Memoirs of an X register
  • maximebeauvais' Blog
  • atari2600land's Blog
  • .:maus:.
  • PAM1234's Blog
  • Nabuko's Den
  • Paranoid's Blog
  • Culmins Development's Blog
  • Atari Joe's Flippin' Sweet Blog
  • When Robots Attack
  • Flack's Daily Smack
  • Jboypacman's Blog
  • neonesmaster's Blog
  • Classic Stories
  • Bruce Tomlin's Blog
  • Beetle's Blog
  • 5-11under's Blog
  • EricDeLee's Blog
  • TunnelRunner's Blog
  • jaymz887's Blog
  • fojy-harakiri's Blog
  • Shroo-man's Blog
  • Ataria51's Blog
  • Mr. Pac-Man's Blog
  • JellE's Dwelling
  • Gaming With Rogmeister
  • Pengwin's Blog
  • neotokeo2001's Blog
  • Arcade's Blog
  • R. Jones' Blog
  • payman84ce's Blog
  • Awed Thoughts
  • super mario 64 level editor
  • Christos' Blog
  • atari_collector's Blog
  • imtron's Blog
  • My Vintage Game collection
  • classicgamingguy's Blog
  • HP Atari King of Michigan's Blog
  • Unknown arcade titles from Fighter17
  • Ain't got time for no Jibbajaba
  • Wickeycolumbus' Blog
  • Ramblings of a moron
  • HatNJ's Blog
  • BlogO
  • ELEKTROTECK
  • bf2k+'s Blog
  • ParaJVE's Blog
  • Cody Rushton's blog
  • It's my life!
  • Bakasama's Blog
  • Dennis V's Blog
  • RaRoss' Blog
  • Collecting Demos
  • Dave Neuman's Blog
  • Borntorun's Blog
  • warren798's Blog
  • Tweety's Blog
  • -^CB^-'s Game Reviews
  • seekingarobiejr's Blog
  • revival studios
  • bust3dstr8's Blog
  • Rom Hunter's Blog
  • Shark05's Blog
  • Lord Helmet's Blog
  • ryanez1's Blog
  • kit's Blog
  • Burma Rocks
  • Bubsy Bobcat Fan Blog
  • Habaki's Blog
  • Dan's Road to 2600 nirvana
  • wccw mark's Blog
  • Hornpipe2's Blog
  • Phantom's Blog
  • Piggles' Blog
  • Dino Dash Derby
  • games_player's Blog
  • 1982VideoGames' Blog
  • Cabbage Patch Kids! Lookin' Great!
  • Confessions of an Aging Gamer...
  • theking21083's Blog
  • retrogeek's Blog
  • Liveinabin's scribbles
  • Cimerians' Blog
  • CollectorVision Blog
  • Ransom's Random Posts
  • www.toyratt.com's Blog
  • RonPrice's Blog
  • s0c7's Blog
  • doyman's Blog
  • DJTekid's Blog
  • EG's code blog
  • kiwilove's Blog
  • 8 Bit Addiction
  • Playing With History
  • simonh's Blog
  • Zereox's Blog
  • Draconland
  • chris_lynx1989's Blog
  • Phuzzed's Blog
  • 7800 NZ's Blog
  • Gamera's Reviews: E.T Coming Soon!
  • Iwan´s Irrational!
  • seemo's Blog
  • The Eviscerator Series
  • Noelio's Blog
  • 480peeka's Blog
  • For Next
  • Take 'Em To The Woodshed
  • bankockor Blog
  • Kelp Entertainment
  • 2600 Fun Blogs
  • PinBlog
  • IHATETHEBEARS' BLOG
  • Atari Fan made Documentary
  • Flashjazzcat's Blog
  • THE 1 2 P's Demo/Import/Gaming Blog
  • STGuy1040's Blog
  • enyalives' Blog
  • Mirage1972's Blog
  • blogs_blog_286
  • The Word Of Ogma
  • GC's blog
  • nanobug's monument of geekiness
  • dogcorn's Blog
  • I Can't Think of a Catchy Title
  • please help and share story
  • ivop's Blog
  • what is the chicago basment
  • Cheat Blog
  • zeropolis79's Blog
  • My video game library
  • the.golden.ax's "Oh my Blog"
  • ValuGamer
  • wolfpackmommy's Blog
  • Z80GUY's Blog
  • jwierer's Blog
  • kroogur's Korner
  • Verbal Compost
  • Frizo's Collecting Adventure!
  • Old School Gamer Review
  • ...
  • Rybags' Blog
  • BDW's Blog
  • tweetmemory's Blog
  • toptenmaterial's Blog
  • grafix's Bit Mouse Playhouse
  • S1500's Blog
  • hackerb9's blog
  • EricBall's Tech Projects (PRIVATE)
  • MagitekAngel's Blog
  • I created this second blog on accident and now I can't figure out how to delete it.
  • keilbaca's Blog
  • TestBot4's Blog
  • Old School Gamer Review
  • The Mario Blog
  • GideonsDad's Blog
  • GideonsDad's Blog
  • GideonsDad's Blog
  • Horst's Blog
  • JIMPACK's Blog
  • Blogpocalypse
  • simonl's Blog
  • creeping insanity
  • Sonic R's Blog
  • CebusCapucinis' Blog
  • Syntax Terror Games
  • NCN's Blog
  • A Wandering Shadow's Travels
  • Arjak's Blog
  • 2600Lives' Blog
  • 2600Lives' Blog
  • Kiwi's Blog
  • Stephen's A8 Blog
  • Zero One
  • Troglodyte's Blog
  • Austin's Blog
  • Robert Hurst
  • This Is Reality Control
  • Animan's Blog Of Unusual Objectionalities
  • Devbinks' Blog
  • a1t3r3g0's Blog
  • The 7800 blog
  • 4Ks' Blog
  • carmel_andrews' Blog
  • iratanam's Blog
  • junkmail's RDE&P Blog
  • Lynxman's FlashCard Blog
  • JagMX's Blog
  • The Wreckening
  • roberto's Blog
  • Incagold's Blog
  • lost blog
  • kurtzzzz's Blog
  • Guitarman's Blog
  • Robert @ AtariAge
  • otaku's Blog
  • otaku's Blog
  • revolutionika's Blog
  • thund3r's Blog
  • edweird13's Blog
  • edweird13's Blog
  • That's what she said.
  • Hitachi's Blog
  • The (hopefully) weekly rant
  • Goochman's Marketplace Blog
  • Marc Oberhäuser's Blog
  • Masquane's AtariAge Blog
  • satan165's Dusty Video Game Museum
  • lazyhoboguy's Blog
  • Retail hell (The EB years)
  • Vectrexer's Blog
  • Game Maker to Game Dev
  • Retro Gaming Corporation
  • Hulsie's Blog
  • Tr3vor's Blog
  • Dryfter's Blog
  • Why Are You Even Reading This?
  • Xuel's Blog
  • GamingMagz
  • travelvietnam's Blog
  • pacmanplayer's Blog
  • TheLunarFox's Blog
  • caver's Blog
  • Atari 2600 for sale with 7 games 2 controllers
  • A Ramblin' Man
  • toiletunes' Blog
  • Justin Payne's Blog
  • ebot
  • Markvergeer's Blog
  • GEOMETRY WARS ATARI 2600
  • LEW2600's Blog
  • Pac-Man Vs Puck-Man's Blog
  • Bri's House
  • Les Frères Baudrand's Blog
  • Secure Your E-Commerce Business With ClickSSL.com
  • raskar42
  • The P3 Studio
  • Bydo's Blog
  • defender666's Blog
  • TheSSLstore - SSL certificates Validity
  • Chuplayer's Blog
  • pacman100000's Blog
  • POKEY experiments
  • JPjuice23's Blog
  • Gary Mc's Blog
  • arkade kid's Blog
  • MaXStaR's Blog
  • SUB HUNTER in A8
  • ScumSoft's Blog
  • The Social Gamer
  • Ping. Pong. Ping. Pong.
  • kgenthe's Blog
  • mapleleaves' Blog
  • Dallas' Blog
  • bfg.gamepassion's Blog
  • Esplonky's Blog
  • Fashion Jewellery's Blog
  • Gabriel's Blog
  • CJ's Ramblings
  • Dastari Creel's Blog
  • dobidy's Blog
  • dragging through the retro streets at dawn
  • Please Delete - Created by Accident
  • Nerdbloggers
  • Algus' Blog
  • Jadedrakerider
  • Appliciousblog.com
  • frederick's Blog
  • longleg's Blog
  • Brain droppings...
  • Sandra's blog
  • Bastelbutze
  • polo
  • VectorGamer's Blog
  • Maybe its a Terrible Tragedy
  • Guru Meditation
  • - - - - - -
  • The 12 Turn Program: Board Game Addiction and You
  • Tezz's projects blog
  • chonglily's Blog
  • masseo1's Blog
  • DCUltrapro's Blog
  • Disjaukifa's Blog
  • Vic George 2K3's Blog
  • Whoopdeedoo
  • ge.twik's Blog
  • DJT's High Score Blog [Test]
  • Disjaukifa's Assembly Blog
  • GonzoGamer's Blog
  • MartinP's Blog
  • marshaz's Blog
  • Pandora Jewelry's Blog
  • Blues76's Blog
  • Adam24's AtariAge Blog!
  • w1k's Blog
  • 8-bit-dreams' Blog
  • Computer Help
  • Chris++'s Blog
  • an atari story
  • JDRose
  • raz0red's Blog
  • The Forth Files
  • The Forth Files
  • A.L.L.'s Blog
  • Frankodragon's Blog Stuffs
  • Partyhaus
  • kankan313rd's Blog
  • n8littlefield's Blog
  • joshuawins99's Blog
  • ¡Viva Atari!
  • FujiSkunk's Blog
  • The hunt for the PAL Heavy Sixer
  • Liduario's Blog
  • kakpu's Blog
  • HSC Experience
  • people to fix atari Blog
  • Gronka's Blog
  • Joey Z's Atari Projects
  • cncfreak's Blog
  • Ariana585's Blog
  • 8BitBites.com
  • BrutallyHonestGamer's Blog
  • falcon_'s Blog
  • lushgirl_80's Blog
  • Lynx Links
  • bomberpunk's Blog
  • CorBlog
  • My Ideas/Rants
  • quetch's Blog
  • jamvans game hunting blog
  • CannibalCat's Blog
  • jakeLearns' Blog
  • DSC927's Blog
  • jetset's Blog
  • wibblebibble's Basic Blog
  • retrovideogamecollector's Blog
  • Sonny Rae's Blog
  • The Golden Age Arcade Historian
  • dianefox's Blog
  • DOMnation's Blog
  • segagamer99's Blog
  • RickR's Blog
  • craftsmanMIKE's Blog
  • gorf68's Blog
  • Gnuberubs Sojourn Dev Journal
  • B
  • iesposta's Blog
  • Cool 'n' Crispy: The Blog of Iceberg_Lettuce
  • ahuffman's Blog
  • Bergum's Thoughts Blog
  • marminer's Blog
  • BubsyFan101 n CO's Pile Of Game Picks
  • I like to rant.
  • Cleaning up my 2600
  • AnimaInCorpore's Blog
  • Space Centurion's Blog
  • Coleco Pacman Simulator (CPMS)
  • ianoid's Blog
  • HLO projects
  • Retro Junky Garage
  • Sega Genesis/Mega Drive High Score Club
  • Prixel Derp
  • HuckleCat's Blog
  • AtariVCS101's Blog
  • Tales from the Game Room's Blog
  • VVHQ
  • Antichambre's Blog
  • REMOVED BY LAW AUTHORITY
  • Synthpop Universe
  • Atari 5200 Joystick Controllers
  • Top 10 Atari 2600 Games
  • Is Atari Still Cool?
  • Buying Atari on Ebay
  • matosimi's Blog
  • GadgetUK's Blog
  • The StarrLab
  • Scooter83 aka Atari 8 Bit Game Hunters' Blog
  • Buddpaul's Blog
  • TheGameCollector's Blog
  • Gamming
  • Centurion's Blog
  • GunsRs7's Blog
  • DPYushira's Entertainment Blog
  • JHL's Blog
  • Intellivision Pierce's Blog
  • Manoau2002 Game and Vinyl Blog
  • Diamond in the Rough
  • Linky's Blog
  • flashno1's Blog
  • Atari 2600 Lab
  • jennyjames' Blog
  • scrottie's Blog
  • Draven1087's Blog
  • Omegamatrix's Blog
  • MegaData Manifesto
  • Selling Atari on Ebay.
  • Unfinished Bitness
  • TI-99/4A Stuff
  • eshu's blog
  • LaXDragon's Blog
  • GozAtari8
  • Bio's Blog of Randomness
  • Out of the Pack
  • Paul Lay's Blog
  • Make Atari 2600 games w/o programming!
  • Rudy's Blog
  • kenjennings' Blog
  • The Game Pit
  • PShunny's Blog
  • Ezeray's Blog
  • Atari 2600 game maps
  • Crazy Climber Metal
  • Keith Makes Games
  • A virtual waste of virtual space
  • TheHoboInYourRoom's Blog
  • Msp Cheats Tips And Techniques To Create You A Better Gamer
  • Tursi's Blog
  • F#READY's Blog
  • bow830
  • Gernots A500 game reviews
  • Byte's Blog
  • The Atari Strikes Back
  • no code, only games now
  • wongojack's Blog
  • Lost Dragon's Blog
  • Musings of the White Lion
  • The Usotsuki Crunch
  • Gunstar's Blogs
  • Lesles12's Blog
  • Atari Randomness
  • OLD CS1's Blog
  • waterMELONE's Blog
  • Flickertail's Blog
  • Dexter's Laboratory Blog
  • ATASCI's Blog
  • ATASCI's Blog
  • --- Ω ---'s Blog
  • mourifay's Blog
  • Zsuttle's gaming adventures
  • Doctor Clu's Space Shows
  • TWO PRINTERS ONE ADAM
  • Atari Jaguar Game Mascots
  • Learning fbForth 2.0
  • splendidnut's Blog
  • The Atari Jaguar Game by Game Podcast
  • Syzygy's Story Blog
  • Atarian Video Game Reviews
  • Caféman's Blog
  • IainGrimm's Blog
  • player1"NOT"ready's Blog
  • Alexandru George's Blog
  • BraggProductions' Blog
  • XDK.development present Microsoft Xbox One Development
  • Song I Wake Up To
  • Jeffrey.Shamblin's Blog
  • Important people who shaped the TI 99/4A World
  • My blog of stuff and things
  • David Vella's Blog
  • Osgeld's Blog
  • CyranoJ's ST Ports
  • InnovaX5's Blog
  • Star_Wars_Collector
  • Alp's Art Blog
  • Excali-blog
  • STGraves' Blog
  • Retro VGS Coleco Chameleon Timeline
  • Geoff Retro Gamer
  • Geoff1980's Blog
  • Coleco Mini
  • Coleco Mini
  • 7399MGM's Blog
  • 7399MGM's Blog
  • doubledragon77's Blog
  • Ballblogɀer
  • pitfallharry95's Blog
  • BawesomeBurf's Blog
  • Fultonbot's Atari Blog
  • Dmitry's Blog
  • Kaug Neatos Crash Bandicoot Bandwagon
  • lexmar482's Blog
  • vegathechosen's Blog
  • Atari 2600JS
  • Doctor Clu's Dissertations
  • schmitzi's Blog
  • BNE Jeff's Blog
  • AverageSoftware's Development Blog
  • FireBlaze's Blog
  • Atarimuseum.nl
  • Vorticon's Blog
  • TurkVanGogH GameZ's Blog
  • bow830's Blog
  • Arcade Attack - Retro Gaming Blog
  • MrRetroGamer's Blog
  • GG's Game Dev, Homebrew Review, Etc. Log
  • dazza's arcade machine games
  • Alcor450's Blog
  • The Outback
  • -^CroSBow^-'s Hardware Videos
  • Captain's Blog
  • Memoirs of a Novelty Account
  • newcoleco's Random Blog
  • Second-Hand Shop
  • Doctor Clu's BBS Trotter
  • Lunar eclipse of the mind
  • simon2014's Blog
  • PhilipTheWhovian's Blog
  • Troff the Shelf
  • jacobus Indev
  • Pac & Pal for the Atari 2600 fan project
  • drawscreen then reset
  • Retrogaming Ramblings
  • G-type's Blog
  • Blog o' Buttons
  • DarQ Massacres' Atari 2600 collection
  • FireStarW's Blog
  • Bobbety_F's Blog
  • Rose-Tinted Recollections
  • Young Guy Experiencing Atari
  • Gray Defender's Blog
  • atasciiview
  • 2600 games worse then E.t
  • ZippyRedPlumber's Blog
  • game_escape's Blog
  • Jackel192's Blog
  • The UAV Blog
  • MykGerard
  • OS9Dude's Blog
  • FPGA video game console
  • darryl1970's Blog
  • Funkmaster V's Gettin' Hip with tha Atari 7800
  • AtariMI1978's Blog
  • AtariMI1978's Blog
  • vidak's Blog
  • 8-bit Computer System Colors in Food Coloring
  • WebSiteRing
  • The Best Assembly Computer
  • As time goes by ...
  • Atari 2600 Collection Bulk Box/ Cartridge Sale
  • T.R.A.S.H Blog
  • goodlasers' Blog
  • GauntletKing2878's Blog
  • My Inner Geek
  • A Raccoon's Retrocade Romp - AA Edition
  • homeboy's Blog
  • ThatAtomCat's Blog
  • Hawk's Blog
  • Bryan's Random Stuff
  • Developing Atari Programs on the Atari 800
  • Eltigro's Blog
  • Memories Limited to 640KB
  • my journey to completing the entire Atari libaray
  • Roblox
  • Question for Homebrew publishers
  • zilog_z80a's Blog
  • Return of the Bobcat
  • deepthaw's Blog
  • Little bit of this and little bit of that
  • Shannon's Blog
  • DoctorSpuds Reviews Things
  • Atari Portfolio Page On Facebook
  • azure's Blog
  • The Atari Kid
  • Alien Isolation Blog
  • Atari_Ace's Blog
  • AtariAdventure's Blog
  • AtariCrypt
  • acsabo's Blog
  • Bioshock Text adventure
  • AtariAdventure Reviews
  • Infinite Warfare Specialist
  • Karl's Blog
  • Bjorkinator's Babbles
  • DZ-Jay's Random Blog
  • CX40Hero's Blog
  • Heroes & Shadows Dev Blog
  • Empty
  • GoldLeader's Blog
  • Adventures in CC65
  • CS2X C# on Atari
  • pboland's Blog
  • Matts's Blog
  • orrko8791's Blog
  • orrko8791's Blog
  • Revontuli's Blog
  • Not Steve's Blog
  • Not Steve's Blog
  • SPACE ROANOKE
  • My life
  • skycop's Blog
  • cessnaace's Blog
  • Omegasupreme's Blog
  • Atari 2600 A/V Mods Wiki
  • Mike Harris' Blog
  • Skwrl63's Blog
  • sometimes99er
  • Mallard Games Development Blog
  • Regaining an Obsession
  • Psi-5
  • The Atari Journals
  • Herovania
  • TBA
  • Bluejay Records Co.
  • Running On Fumes
  • Mozartkügel's Midnight Retro Development
  • Alcadon
  • baktra
  • Flojomojo's Simple Mind
  • MarkO
  • Lazydead's Loose Ends
  • OldSchoolRetroGamer's Bloggy Nonsense
  • Magmavision After Dark
  • My Homebrew Devlog
  • BUBSY Blogs [blank]
  • Too young for Atari, too old for XBox
  • KC-ACE Blog
  • Brown Altitude Bar
  • Bubsy TV Pilot Wiki
  • Poltergeist
  • Projektstunde
  • bluejay's corner of random shit
  • SpornyKun
  • alex_79's Blog
  • Atari Label Reproduction/ Relabeling
  • Ephemeral
  • My opinion and story about Atari 2600
  • Sony PlayStation 5/PS5™ Development Kit (Dev Kit) for SALE
  • Delete
  • Superkitten
  • Doublediwn
  • Reindeer Flotilla
  • Intellivision hacks (.cfg files)
  • My Experience Learning 68k Assembly
  • My Atari Projects
  • Writing is hard
  • My Atari 2600 Collection
  • Jodi C. Kirby's blog
  • Power outage a few days ago
  • Sony PlayStation 5/PS5™ Development Kit (Dev Kit) for SALE
  • xNeoGeo1982Blogx
  • The Ivory Tower Collections 7800s
  • Incognito Atari 800 step by step pictorial install tutorial/guide including ATR swap button mod
  • Cree's Stories
  • Testing
  • NeonPeon's (Mark W's) Adventures in programming for Vectrex
  • Stories from the -: ITC :-
  • Gameboy & dress up games
  • BRP's random dev journaling
  • My PC-Engine/TurboGrafx-16 Projects
  • Ivory Tower Technical Notes
  • Programming a game..
  • Games People Play
  • Atari 8-bit Memories, Ideas, and Active Projects
  • WEATHER REPORT
  • Biff's Blasts
  • Programming Journey
  • CREE BENNET DOESN'T CARE
  • Mark W Plays Old Games on a Thursday
  • 35 Years, 9 Months and 16 Days in the Life Of...
  • IntellivisionRevolution's Blog
  • Atari BBS Gurus's News
  • On Duty's Blog
  • The official Robin Gravel's club's Archive
  • Bowling's Blog
  • Lawnmover's Blog
  • Null's null
  • Null's Blog
  • KC-ACE Reboot's KC-ACE Reboot Blog
  • Wizzy's Concept and Theme
  • Wizzy's Form
  • Wizzy's Moodboard
  • Wizzy's Space
  • Wizzy's Magical objects
  • Wizzy's Progress
  • Wizzy's At home
  • Wizzy's Halloween
  • Wizzy's Equipping
  • Wizzy's Mentor
  • Wizzy's World
  • Wizzy's Trials
  • Wizzy's Characters
  • Alternate Reality's Blog

Calendars

  • AtariAge Calendar
  • The Club of Clubs's Events
  • Atari BBS Gurus's Calendar

Find results in...

Find results that contain...


Date Created

  • Start

    End


Last Updated

  • Start

    End


Filter by number of...

Joined

  • Start

    End


Group


Website


Facebook


Twitter


Instagram


YouTube


eBay


GitHub


Custom Status


Location


Interests


Currently Playing


Playing Next

Found 23 results

  1. I thought I'd post my notes (so far) on using MAC/65 and BUG/65 from Sparta DOS 3.2g on my 576NUC+ (essentially like my old 130XE, but so much more with FujiNet). Much of this has been gleened from Atari Age posts (numerous and thank you), Mapping the Atari, the Sparta DOS Construction Set manual, and a little bit of experimentation. It helps me to put it all in one place. Hopefully it will save some time for someone else ... Movement between programs is not as smooth as with the cartridge MAC/65 version, but it is quite usable once you learn the idiosyncrasies and put together a few patches (kludges). I welcome any questions, suggestions, or insights. Please note that with my hardware cartridges are not an option. MAC/65 Usage Notes (with Sparta DOS 3.2g) Screen Color: Using the change command within Mac/65 for background/text colours: C 8B3F<82 for background C 8B44<0A for text luminance {00,02,...,0E} Rentry Point: After exiting to DOS, you can renter MAC65 with a simple RUN, only if no other executable has LOADed or RUN, which updates RUNLOC. You can still renter (if the MAC65 memory has not been altered) with RUN 8B1C Making a small program to help with rentry: 10 ; LIST #D:REMAC.ASM 20 ;############################ 30 ; 40 ; JUMP BACK INTO MAC/65 FROM 50 ; DOS. RUN FROM DATA AREA 60 ; OF THE CASSETTE BUFFER. 70 ; 80 ;############################ 90 ; 0000 0100 *= $0400 0400 201C8B 0110 JSR $8B1C 0403 60 0120 RTS 0404 0130 .END This is assembled with MAC/65 to REMAC.COM and put in the root directory. It can also be done by creating a Sparta DOS *.BAT file, but the executable is a little neater (a matter of preference). BUG/65 Usage Notes (with Sparta DOS 3.2g) Directory: In Sparta DOS 3.2g using BUG/65 returns to the root directory. Move binary files to another drive (or ramdrive) to access when running BUG/65. Color Scheme: To match the MAC65 color scheme you can run: F 2C6 2C6 00 F 2C8 2C8 00 F 2C5 2C5 08 Cursor Loss: The cursor is lost on exit to DOS (Q). It can be restored by pressing the BREAK key. Rentry Point: $200 above load address. For example, To renter BUG65 6000, you can type RUN 6200 To speed up the color adjustments I have created a BUG/65 script in a file named PREF.BUG, and execute it from BUG/65 with E #D:PREF.BUG . Mapping the Atari: https://www.atariarchives.org/mapping/memorymap.php Sparta DOS Construction Set: https://atariwiki.org/wiki/Wiki.jsp?page=Sparta DOS Construction Set Manual
  2. Dear All! This is our "Atari 8-bit Programming" Discord server. It is a twin Discord server to the Fujinet Discord. Here is an invitation: https://discord.gg/GTapZjCsgp Best, Peter Kaczorowski
  3. Hi together! First of all, we will give Charles W. Marslett a Zotta (10^24) thank you for all his work he has done and another one for giving us the source code of his work into PD. Charles, from all 5 continents from all Atari users: Thank you so much!!! After a long search and loop verfication with Charles, we now can offer you: FAST FLOATING POINT source code for the ATARI, Revision F The first publication was made in 1981, improved and adapted to more and more Atari computers over the years. With Charles's work it was possible for the first time officially to make reliable calculations! All this up to 3.5 times faster than the original Atari rom for the floating point routines from $D800 to $DFFF. Another great advantage: all addresses for the floating point routines are the same as in the original Atari one! With the now final version F, sorry to say, all Atari OSs need to be vaccinated... Luckily, this can be done in just one shot by replacing the specific OS rom. Please take into account, Charles did this in 1981, while: https://en.wikipedia.org/wiki/IEEE_754 is from 1985 on... This shows how far ahead of time Charles was and still is! For the gamers this could be a nice increase in calculation speed, like: https://en.wikipedia.org/wiki/Fast_inverse_square_root in the game Doom later. For serious calculations, this is a no miss under all circumstances. We further would like to thank Robert "Bob" Puff for translating the original AMAC source code from Charles to the MAC/65 and drac030 for finding the very last byte to be changed. A big thank you goes to the University of Michigan for hosting the file: faschips.arc Have fun and warm up the EPROM bruners... All the best.
  4. Hi All, I will be selling my collection, as a bundle. There are some gems in there. Atari Roots for one, Mac/65 with manual and big box Robotron. Also a spare NOS 400 keyboard, and an 800XL mobo. 2 USB power for XL series adapters and an XIO to USB. I am looking for reasonable offers. I live in the Los Angeles Area. Thank you. KB
  5. Bit Operations ============================================================== Part 1 - Introduction http://atariage.com/forums/blog/576/entry-13175-part-1-of-11-simple-assembly-for-atari-basic/ Part 2 - Learn 82.7% of Assembly Language in About Three Pages http://atariage.com/forums/blog/576/entry-13176-part-2-of-11-simple-assembly-for-atari-basic/ Part 3 - The World Inside a USR() Routine http://atariage.com/forums/blog/576/entry-13177-part-3-of-11-simple-assembly-for-atari-basic/ Part 4 - Implement DPEEK() http://atariage.com/forums/blog/576/entry-13178-part-4-of-11-simple-assembly-for-atari-basic/ Part 5 - Implement DPOKE http://atariage.com/forums/blog/576/entry-13180-part-5-of-11-simple-assembly-for-atari-basic/ Part 6 - Various Bit Manipulations http://atariage.com/forums/blog/576/entry-13181-part-6-of-11-simple-assembly-for-atari-basic/ Part 7 - Convert Integer to Hex String http://atariage.com/forums/blog/576/entry-13182-part-7-of-11-simple-assembly-for-atari-basic/ Part 8 - Convert Integer to Bit String http://atariage.com/forums/blog/576/entry-13183-part-8-of-11-simple-assembly-for-atari-basic/ Part 9 - Memory Copy http://atariage.com/forums/blog/576/entry-13184-part-9-of-11-simple-assembly-for-atari-basic/ Part 10 - Binary File I/O Part 1 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13185-part-10-of-11-simple-assembly-for-atari-basic/ Part 11 - Binary File I/O Part 2 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13186-part-11-simple-assembly-for-atari-basic-the-end/ ============================================================== The fun and games in programming for the Atari environment often requires manipulating hardware registers. Many features are enabled and disabled by individual bits in a byte. Additionally, the data used for many graphics features in the Atari 8-bit environment makes more sense when dealt with at the bit level. For instance, a character on the Atari's normal text screen (ANTIC Mode 2) can be flipped between inverse video and normal video by switching the high bit in the character byte on and off. The same bit in ANTIC modes 4 and 5 chooses between COLPF2 or COLPF3 color for certain bit patterns in the character data. The text color in ANTIC modes 6 and 7 is controlled by manipulating the two highest bits of the character byte. Pixels in a map mode are specified by single bits, pairs of bits, or groups of four bits. Bit-wise operations, simple in 6502 machine code, are painful and very slow in BASIC. Isolating a byte's individual bits in BASIC code is a horrible sight to behold. This is nothing like the two-byte DPeek and DPoke that BASIC can duplicate (slowly) with a multiply or divide. Bit work in BASIC requires repeated value testing and computation to identify which bits are set and which are clear. A simple example of the terror is presented here: 100 REM DISSECT BITS IN 16-BIT WORD 105 REM SHOWBITS.BAS 110 REM 115 WORD=35235:REM $89A3 120 BVAL=32768:REM $8000 125 ? "WORD = ";WORD 130 ? "BITS ="; 135 FOR BIT=0 TO 15 140 IF INT(BIT/4)=BIT/4 THEN ? " "; 145 IF WORD>=BVAL THEN ? "1";:WORD=WORD-BVAL:GOTO 155 150 ? "0"; 155 BVAL=BVAL/2 160 NEXT BIT 165 ? 170 END The code starts with the value of the highest (that is, 16th) bit of a word. If the test value is greater than or equal to the bit value then the bit is set. If not, then the bit is 0. Then the value of the bit position is divided by 2 to get the value of the next position. This evaluation and test action loops 16 times until all the bits are evaluated. One optional extra is inserted – before every four bits it outputs a space to separate the bits into groups corresponding to nybbles (half bytes). In terms of code this is about as simple as it gets. The routine could be made faster by packaging the bit values in an array and testing from the array. But the same problem remains: BASIC is looping and performing multiple operations in each loop. In this simple example it is scanning one 16-bit integer. Implementing useful bit operations requires dissecting two 16-bit integers and assembling output in another 16-bit integer. We really don't want to go there in BASIC. In defense of Atari BASIC, though it lacks bit operations, so do most other BASIC languages, because the original purpose of BASIC is to protect beginner programmers from the hardware. But, we're working on an Atari here and want to embrace the hardware. OSS BASIC XL provides several operator symbols for actions on bits: & == Bit-wise AND two 16-bit values ! == Bit-wise OR two 16-bit values % == Bit-wise EOR (Exclusive OR) two 16-bit values The 6502 supports these operations and also supports other bit manipulations that BASIC XL (and other more modern BASICs) do not support. Shifting bits and rotating bits in a byte are simple in the 6502. These could be interesting in a utility for BASIC. So, we will plan for machine language routines that do bitwise AND, OR, EOR operations and also support left and right shift, and left and right rotate. Since the USR() environment automatically provides 16-bit argument values to the machine language code a choice needs to be made: implement the routines to work on 16-bit values or just a single byte. In the case of AND, OR, and EOR there's little difference between supporting a byte or 16-bit operation. If the supplied argument values are only 8-bits (with 0 as the high byte) then the resulting computation will also appear to be 8-bits. Rotating and Shifting bits is a different situation. The results of shifting and rotating an 8-bit value is different from the same action on a 16-bit value. In most situations the Atari environment uses 8-bits as data (character sets, map graphics, player/missile data), so these bit operations are more likely to be useful on just a byte. Below is the list of operations to design. Each operation has two values, an operator, and a return value: Result == 16-bit value AND 16-bit value Result == 16-bit value OR 16-bit value Result == 16-bit value EOR 16-bit value Result == 8-bit value RIGHT SHIFT number of bits Result == 8 bit value LEFT SHIFT number of bits Result == 8-bit value RIGHT ROTATE number of bits Result == 8-bit value LEFT ROTATE number of bits Given the earlier Dpeek and Dpoke examples most readers are probably not looking forward to the tedious repetition of the argument setup and validation code for seven similar, separate routines. Nothing like that will happen. Instead, all the operations can be combined into one utility. This is simple (and sensible) to do, because all these operations use the same number of arguments and all return a value to BASIC. Since BASIC converts all arguments to 16-bit values, the operations that work on 8-bit values will use only the low byte of the value and ignore the high byte. The reader will be relieved to learn there will be just one instance of the initialization code for managing the stack and arguments. This is where the perceived overkill factor of the generic initialization code pays off. The next problem is how to choose one of seven different operations in one machine language utility. There are always several ways to solve a problem. One possibility is that each routine will have its own entry point relative to the start. Each bit operation routine would have to call the initialization and then somehow find its way back to the proper bit operation code. This has the problem that it is difficult to share the initialization code between all the operations and keep the routines relocatable. Simple relative branching keeps the code relocatable, but using JSR (Jump To Subroutine) to call the initialization code or the actual routines makes the code non-relocatable without a heap of other work. Another possibility is that the machine language routine uses the value identifying the bit operation as the basis for branching to the specific routine. This has more promise. It means the machine language code will test the argument for each bit operation identifier and then branch accordingly. A table of addresses for JMP or JSR would be faster, but it would not be easily relocatable. Keeping the code relocatable requires the entry for all routines fall within branching range of the decision making code. Generally speaking, the maximum distance of a branch is plus or minus about 127 bytes. The assembler is always more than happy to tell the programmer when target code is out of range of a branch. These bit-wise features are simple concepts for 6502 machine language, so the routines will not be large, but even if they were, there are ways around the problem of the half-page branch distance. BITS in Mac/65 Assembler Code 1000 ; BITS.M65 1005 ; 1010 ; Perform bit level operations 1015 ; for Atari BASIC. 1020 ; 1025 ; USR 3 arguments: 1030 ; Oper == Operation (1,2,...7) 1035 ; Val1 == First value. 1040 ; Val2 == Second Value. 1045 ; 1050 ; Operations: 1055 ; 1 = 16-bit Val1 OR 16-bit Val2 1060 ; 2 = 16-bit Val1 AND 16-bit Val2 1065 ; 3 = 16-bit Val1 EOR 16-bit Val2 1070 ; 4 = 8-bit Val1 LSR 8-bit num bits Val2 1075 ; 5 = 8-bit Val1 LSL 8-bit num bits Val2 1080 ; 6 = 8-bit Val1 ROR 8-bit num bits Val2 1085 ; 7 = 8-bit Val1 ROL 8-bit num bits Val2 1090 ; 1095 ; USR return value is the result. 1100 ; 1105 ; Use the FR0/FR1 FP register. 1110 ; The return value for BASIC 1115 ; goes in FR0. 1120 ; No FP is used so all of FR0 1125 ; (and more FP registers) can 1130 ; be considered available. 1135 ; 1140 ZRET = $D4 ; FR0 $D4/$D5 Return value 1145 ZARGS = $D5 ; $D6-1 for arg Pulldown loop 1150 ZVAL2 = $D6 ; FR0 $D6/$D7 Value 1155 ZVAL1 = $D8 ; FR0 $D8/$D9 Value 1160 ZOPER = $DA ; FR1 $DA/$DB Operation 1165 ; 1170 ; Define operations1175 ; 1180 OPER_OR = $01 1185 OPER_AND = $02 1190 OPER_EOR = $03 1195 OPER_LSR = $04 1200 OPER_LSL = $05 1205 OPER_ROR = $06 1210 OPER_ROL = $07 1215 ; 1220 .OPT OBJ 1225 ; 1230 ; Arbitrary. This is relocatable. 1235 ; 1240 *= $9500 1245 ; 1250 INIT 1255 LDA #$00 ; Make sure return 1260 STA ZRET ; value is cleared 1265 STA ZRET+1 ; by default. 1270 PLA ; Get argument count 1275 TAY 1280 BEQ EXIT ; Shortcut for no args 1285 ASL A ; Now number of bytes 1290 TAY 1295 CMP #$06 ; Value1, Value2, Oper 1300 BEQ PULLDOWN 1305 ; 1310 ; Bad args. Clean up for exit. 1315 ; 1320 DISPOSE ; any number of args 1325 PLA 1330 DEY 1335 BNE DISPOSE 1340 RTS ; Abandon ship 1345 ; 1350 ; This code works the same 1355 ; for 1, 4, 8 ... arguments. 1360 ; 1365 PULLDOWN 1370 PLA 1375 STA ZARGS,Y 1380 DEY 1385 BNE PULLDOWN 1390 ; 1395 ; Like a Switch/Case 1400 ; 1405 LDY ZOPER 1410 BEQ EXIT ; Zero operator 1415 ; 1420 DEY ; #$01 OR 1425 BEQ DO_OR 1430 ; 1435 DEY ; #$02 AND 1440 BEQ DO_AND 1445 ; 1450 DEY ; #$03 EOR 1455 BEQ DO_EOR 1460 ; 1465 DEY ; #$04 LSR 1470 BEQ DO_LSR 1475 ; 1480 DEY ; #$05 LSL 1485 BEQ DO_LSL 1490 ; 1495 DEY ; #$06 ROR 1500 BEQ DO_ROR 1505 ; 1510 DEY ; #$07 ROL 1515 BEQ DO_ROL 1520 ; 1525 EXIT 1530 RTS ; bye. 1535 ; 1540 ; 1 = 16-bit Val1 OR 16-bit Val2 1545 DO_OR 1550 LDA ZVAL1 1555 ORA ZVAL2 1560 STA ZRET 1565 LDA ZVAL1+1 1570 ORA ZVAL2+1 1575 STA ZRET+1 1580 RTS 1585 ; 1590 ; 2 = 16-bit Val1 AND 16-bit Val2 1595 DO_AND 1600 LDA ZVAL1 1605 AND ZVAL2 1610 STA ZRET 1615 LDA ZVAL1+1 1620 AND ZVAL2+1 1625 STA ZRET+1 1630 RTS 1635 ; 1640 ; 3 = 16-bit Val1 EOR 16-bit Val2 1645 DO_EOR 1650 LDA ZVAL1 1655 EOR ZVAL2 1660 STA ZRET 1665 LDA ZVAL1+1 1670 EOR ZVAL2+1 1675 STA ZRET+1 1680 RTS 1685 ; 1690 ; 4 = 8-bit Val1 LSR 8-bit num bits Val2 1695 DO_LSR 1700 LDA ZVAL1 1705 STA ZRET 1710 LDX ZVAL2 1715 BEQ EXIT 1720 LSR_LOOP 1725 LSR A 1730 DEX 1735 BNE LSR_LOOP 1740 STA ZRET 1745 RTS 1750 ; 1755 ; 5 = 8-bit Val1 LSL 8-bit num bits Val2 1760 DO_LSL 1765 LDA ZVAL1 1770 STA ZRET 1775 LDX ZVAL2 1780 BEQ EXIT 1785 LSL_LOOP 1790 ASL A 1795 DEX 1800 BNE LSL_LOOP 1805 STA ZRET 1810 RTS 1815 ; 1820 ; 6 = 8-bit Val1 ROR 8-bit num bits Val2 1825 DO_ROR 1830 LDA ZVAL1 1835 STA ZRET 1840 LDX ZVAL2 1845 BEQ EXIT 1850 ROR_LOOP 1855 CLC 1860 ROR A 1865 BCC OVER_ROR_INC 1870 ORA #$80 1875 OVER_ROR_INC 1880 DEX 1885 BNE ROR_LOOP 1890 STA ZRET 1895 RTS 1900 ; 1905 ; 7 = 8-bit Val1 ROL 8-bit num bits Val2 1910 DO_ROL 1915 LDA ZVAL1 1920 STA ZRET 1925 LDX ZVAL2 1930 BEQ EXIT 1935 ROL_LOOP 1940 CLC 1945 ROL A 1950 BCC OVER_ROL_INC 1955 ORA #$01 1960 OVER_ROL_INC 1965 DEX 1970 BNE ROL_LOOP 1975 STA ZRET 1980 RTS 1985 ; 1990 .END Now for the take-apart. The common initialization has these extra lines of code: 1255 LDA #$00 ; Make sure return 1260 STA ZRET ; value is cleared 1265 STA ZRET+1 ; by default. The program starts by clearing the response data. Since early exit may occur for different reasons in different places of the code, it makes sense to do this once rather than in several different places. When the code finds an error it can just return (RTS) to BASIC. There is nothing new with the DISPOSE and PULLDOWN loops – they are the same as prior examples. The next section is a simple switch/case-like decision blocks to identify which operator to use: 1395 ; Like a Switch/Case 1400 ; 1405 LDY ZOPER 1410 BEQ EXIT ; Zero operator 1415 ; 1420 DEY ; #$01 OR 1425 BEQ DO_OR 1430 ; 1435 DEY ; #$02 AND 1440 BEQ DO_AND . . . 1510 DEY ; #$07 ROL 1515 BEQ DO_ROL Zero is not a valid operation identifier, so that can be discarded quickly. Ordinarily, testing a list of values would look something like this which requires four bytes for each test: CPY #$01 BEQ DO_OR Since operator identifiers are sequential beginning from 1 the code can use a shortcut – it decrements the operator identifier and branches when reaching zero to the appropriate routine. This is still two instructions, but only three bytes (for a total of 21 bytes rather than 28 bytes to test 7 values). The OR, AND, and EOR routines are similar: 1540 ; 1 = 16-bit Val1 OR 16-bit Val2 1545 DO_OR 1550 LDA ZVAL1 1555 ORA ZVAL2 ; <- OR 1560 STA ZRET 1565 LDA ZVAL1+1 1570 ORA ZVAL2+1 ; <- OR 1575 STA ZRET+1 1580 RTS The only difference between each routine is the actual 6502 operation (ORA, AND, EOR) combining Value 1 and Value 2 which it then outputs to the return value for BASIC. The shift routines must loop since the 6502 shift instructions move the data by only one bit: 1690 ; 4 = 8-bit Val1 LSR 8-bit num bits Val2 1695 DO_LSR 1700 LDA ZVAL1 1705 STA ZRET 1710 LDX ZVAL2 1715 BEQ EXIT 1720 LSR_LOOP 1725 LSR A ; <- Can only move bits one position at a time 1730 DEX 1735 BNE LSR_LOOP 1740 STA ZRET 1745 RTS Each routine loops to shift only the low byte of Value 1 by the number of bits in Value 2 with the result output in the low byte of the return value for BASIC. Since the initialization cleared the entire return value, these routines do not need to worry about clearing the return value's high byte. The bit rotation routines: 1820 ; 6 = 8-bit Val1 ROR 8-bit num bits Val2 1825 DO_ROR 1830 LDA ZVAL1 1835 STA ZRET 1840 LDX ZVAL2 1845 BEQ EXIT 1850 ROR_LOOP 1855 CLC 1860 ROR A 1865 BCC OVER_ROR_INC 1870 ORA #$80 1875 OVER_ROR_INC 1880 DEX 1885 BNE ROR_LOOP 1890 STA ZRET 1895 RTS This is a little more complicated. The 6502 bit rotations can be thought of as operations using nine bits, not eight. The Carry bit does double duty. It provides the source bit value to move into the byte and then accepts the value of the bit that was rotated out of the byte. The problem here is that the bit moving out of the byte has to immediately be moved back into the other end of the byte. How the code solves this: it forces the Carry bit to zero (CLC) insuring a zero bit always rotates into the byte. After the rotation the code checks the bit that rotated out of the byte (in Carry). If the Carry bit is set then the code turns on the bit that should have rotated into the other end of the byte (here, ORA #$80). Testing BITS The Atari BASIC program below, TESTBITS.BAS, demonstrates the routines: 100 REM TEST BIT OPERATIONS UTILITY 105 REM 110 GRAPHICS 0:POKE 710,0:POKE 82,0 115 DIM B(3,1),N$(21) 120 GOSUB 10000:REM BITS UTILITY 125 RESTORE 155:REM BIT PATTERNS 130 FOR X=0 TO 1 135 FOR Y=0 TO 3 140 READ D:B(Y,X)=D 145 NEXT Y 150 NEXT X 155 DATA 0,257,0,257,0,0,258,258 160 REM ENUMERATE BIT OPERATIONS 165 READ BOR,BAND,BEOR,BLSR,BLSL,BROR,BROL 170 DATA 1,2,3,4,5,6,7 175 N$="OR ANDEORLSRLSLRORROL" 180 REM 185 REM TEST OR, AND, EOR 190 REM 195 FOR OPER=BOR TO BEOR 200 FOR Y=0 TO 3 205 V=USR(BITS,OPER,B(Y,0),B(Y,1)) 210 ? B(Y,0);" "; 215 ? N$(OPER*3-2,OPER*3);" "; 220 ? B(Y,1);" = ";V 225 NEXT Y 230 ? 235 NEXT OPER 240 REM 245 REM TEST SHIFT AND ROTATE 250 REM 255 V=129:REM $81 260 FOR OPER=BLSR TO BROL 265 FOR Y=0 TO 8 270 W=USR(BITS,OPER,V,Y) 275 ? V;" "; 280 ? N$(OPER*3-2,OPER*3);" "; 285 ? Y;" = ";W 290 NEXT Y 295 ? 300 NEXT OPER 305 END 9997 REM 9998 REM SETUP BITS ML UTILITY 9999 REM 10000 DIM BT$(162) 10001 BITS=ADR(BT$) 10002 RESTORE 27000 10003 FOR I=0 TO 161 10004 READ D:POKE BITS+I,D 10005 NEXT I 10006 RETURN 26996 REM H1:BITS.OBJ 26997 REM Size = 162 26998 REM Start = 38144 26999 REM End = 38305 27000 DATA 169,0,133,212,133,213,104,240 27001 DATA 43,10,168,201,6,240,5,104 27002 DATA 136,208,252,96,104,153,213,0 27003 DATA 136,208,249,164,218,240,21,136 27004 DATA 240,19,136,240,29,136,240,39 27005 DATA 136,240,49,136,240,61,136,240 27006 DATA 73,136,240,90,96,165,216,5 27007 DATA 214,133,212,165,217,5,215,133 27008 DATA 213,96,165,216,37,214,133,212 27009 DATA 165,217,37,215,133,213,96,165 27010 DATA 216,69,214,133,212,165,217,69 27011 DATA 215,133,213,96,165,216,133,212 27012 DATA 166,214,240,208,74,202,208,252 27013 DATA 133,212,96,165,216,133,212,166 27014 DATA 214,240,193,10,202,208,252,133 27015 DATA 212,96,165,216,133,212,166,214 27016 DATA 240,178,24,106,144,2,9,128 27017 DATA 202,208,247,133,212,96,165,216 27018 DATA 133,212,166,214,240,158,24,42 27019 DATA 144,2,9,1,202,208,247,133 27020 DATA 212,96 The program starts by building a table of bit combinations for testing OR, AND, and EOR. The non-zero decimal values correspond to integers with one bit set in the high byte, and a bit set in the low byte. However the low byte values are different bits. This helps demonstrate that the bit operations are working on both the high bytes and low bytes, and that the bits are combined as expected by the operation. The code loops through the OR, AND, and EOR operations, and for each operation loops through the table of bits displaying the results of the operations. The next section loops through the shift and rotate operations and for each operation it performs zero through 8 bit shifts displaying the results. Test program output: 0 OR 0 = 0 257 OR 0 = 257 0 OR 258 = 258 257 OR 258 = 259 0 AND 0 = 0 257 AND 0 = 0 0 AND 258 = 0 257 AND 258 = 256 0 EOR 0 = 0 257 EOR 0 = 257 0 EOR 258 = 258 257 EOR 258 = 3 129 LSR 0 = 129 129 LSR 1 = 64 129 LSR 2 = 32 129 LSR 3 = 16 129 LSR 4 = 8 129 LSR 5 = 4 129 LSR 6 = 2 129 LSR 7 = 1 129 LSR 8 = 0 129 LSL 0 = 129 129 LSL 1 = 2 129 LSL 2 = 4 129 LSL 3 = 8 129 LSL 4 = 16 129 LSL 5 = 32 129 LSL 6 = 64 129 LSL 7 = 128 129 LSL 8 = 0 129 ROR 0 = 129 129 ROR 1 = 192 129 ROR 2 = 96 129 ROR 3 = 48 129 ROR 4 = 24 129 ROR 5 = 12 129 ROR 6 = 6 129 ROR 7 = 3 129 ROR 8 = 129 129 ROL 0 = 129 129 ROL 1 = 3 129 ROL 2 = 6 129 ROL 3 = 12 129 ROL 4 = 24 129 ROL 5 = 48 129 ROL 6 = 96 129 ROL 7 = 192 129 ROL 8 = 129 That shows the operations are working as expected. That is, assuming one is willing to sit and visualize the bit patterns corresponding to each decimal value. For these purposes the output is less than ideal. Atari BASIC prints numbers as decimals. There is no built-in option to display bytes and integer values in ways more meaningful for 8-bit computer programming. There must be a better way… (Stay tuned for the next episode.) Below are the source files and examples of how to load the machine language routine into BASIC included in the disk image and archive: BITS File List: BITS.M65 Saved Mac/65 source BITS.L65 Mac/65 source listing BITS.T65 Mac/65 source listed to H6: (linux) BITS.ASM Mac/65 assembly listing BITS.TSM Mac/65 assembly listing to H6: (linux) BITS.OBJ Mac/65 assembled machine language program (with load segments) BITS.BIN Assembled machine language program without load segments BITS.LIS LISTed DATA statements for BITS.BIN routine. BITS.TLS LISTed DATA statements for BITS.BIN routine to H6: (linux) MAKEBITS.BAS BASIC program to create the BITS.BIN file. This also contains the BITS routine in DATA statements. MAKEBITS.LIS LISTed version of MAKEBITS.BAS MAKEBITS.TLS LISTed version of MAKEBITS.BAS to H6: (linux) SHOWBITS.BAS Example BASIC program dissecting bits in a 16-bit integer. SHOWBITS.LIS LISTed version of SHOWBITS.BAS SHOWBITS.TLS LISTed version of SHOWBITS.BAS to H6: (linux) TESTBITS.BAS BASIC program that tests the BITS USR() routines. TESTBITS.LIS LISTed version of TESTBITS.BAS. TESTBITS.TLS LISTed version of TESTBITS.BAS to H6: (linux) ZIP archive of files: Bits_Disk.zip Tar archive of files (remove the .zip after download) Bits_Disk.tgz.zip For everyone who has been born of God overcomes the world. And this is the victory that has overcome the world—our faith. 1 John 5:4
  6. Binary File I/O (Part 2 of 2) ============================================================== Part 1 - Introduction http://atariage.com/forums/blog/576/entry-13175-part-1-of-11-simple-assembly-for-atari-basic/ Part 2 - Learn 82.7% of Assembly Language in About Three Pages http://atariage.com/forums/blog/576/entry-13176-part-2-of-11-simple-assembly-for-atari-basic/ Part 3 - The World Inside a USR() Routine http://atariage.com/forums/blog/576/entry-13177-part-3-of-11-simple-assembly-for-atari-basic/ Part 4 - Implement DPEEK() http://atariage.com/forums/blog/576/entry-13178-part-4-of-11-simple-assembly-for-atari-basic/ Part 5 - Implement DPOKE http://atariage.com/forums/blog/576/entry-13180-part-5-of-11-simple-assembly-for-atari-basic/ Part 6 - Various Bit Manipulations http://atariage.com/forums/blog/576/entry-13181-part-6-of-11-simple-assembly-for-atari-basic/ Part 7 - Convert Integer to Hex String http://atariage.com/forums/blog/576/entry-13182-part-7-of-11-simple-assembly-for-atari-basic/ Part 8 - Convert Integer to Bit String http://atariage.com/forums/blog/576/entry-13183-part-8-of-11-simple-assembly-for-atari-basic/ Part 9 - Memory Copy http://atariage.com/forums/blog/576/entry-13184-part-9-of-11-simple-assembly-for-atari-basic/ Part 10 - Binary File I/O Part 1 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13185-part-10-of-11-simple-assembly-for-atari-basic/ Part 11 - Binary File I/O Part 2 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13186-part-11-simple-assembly-for-atari-basic-the-end/ ============================================================== New XIO in Mac/65 Assembler Code Many articles on this subject go by a simple route – use BASIC code to set up all the IOCB values for the 7/Get Bytes or 11/Put Bytes commands, and then provide a minimal assembly routine that simply calls the CIO vector. While BASIC mangles the binary read/write functions, XIO itself also is sufficiently broken to justify a complete machine language USR() routine that exercises CIO commands the way they were intended. For example, ICAX values are not always needed or even wanted, but XIO requires the values. In fact, ICAX values are rarely needed outside of the 3/Open command. Similarly, the filespec/buffer is often not necessary. This routine will perform the same purpose of XIO, but allow a variable number of arguments, adding more arguments only as needed: Always required (2 arguments): 1) Channel Number – Only low byte values 0 to 7 accepted. The high byte is ignored. 2) CIO Command – Only the low byte of the argument will be used. Optional (4 arguments): 3) Filespec/Buffer address – 16-bit value 4) Filespec/Buffer length – 16-bit value Additionally optional when the Filespec/Buffer is provided (5 arguments): 5) ICAX1 – the low byte of this argument is used. High byte is ignored. Additionally optional when the Filespec/Buffer and ICAX1 are provided (6 arguments): 6) ICAX2 – the low byte of this argument is used. High byte is ignored. Since the routine accepts 2, 4, 5, or 6 arguments it can support any of the following: 2) USR(NEWXIO,6,18) – Perform command 18/Fill for screen device (assuming channel 6) 4) USR(NEWXIO,1,254,ADR(F$),LEN(F$)) - Use channel 1 to format (254) the disk drive described by F$. This would also be the format/number of arguments needed for performing CIO Commands 7/Get Bytes and 11/Put Bytes. 5) USR(NEWXIO,5,3,ADR(F$),LEN(F$),6) - Use channel 5 to open (3) the disk directory (read/4 + directory/2 = 6) described by F$ 6) USR(NEWXIO,6,3,ADR(F$),LEN(F$),28,7) – Use channel 6 to open (3) as graphics mode 7 with a text window (read/4 + write/8 + window/16 = 28) assuming F$ describes “S:” Variable number of arguments means this program is organized differently from the prior utilities that copy stack arguments to Page Zero. Since this is just an interface for putting values into the IOCB it doesn't make use of Page Zero beyond returning a status value to BASIC. 0100 ; NXIO.M65 0105 ; 0110 ; NEW CIO/XIO INTERFACE 0115 ; 0120 ; SETUP IOCB WITH THE SUPPLIED 0125 ; ARGUMENTS AND CALL CIO 0130 ; 0135 ; USR 2, 4, 5, or 6 ARGUMENTS: 0140 ; CHANNEL == IOCB CHANEL (LOW BYTE) 0145 ; COMMAND == CIO COMMAND (LOW BYTE) 0150 ; BUF ADR == ADDRESS OF BUFFER 0155 ; BUF LEN == LENGTH OF BUFFER 0160 ; ICAX1 == CIO ICAX1 VALUE (LOW BYTE) 0165 ; ICAX2 == CIO ICAX2 VALUE (LOW BYTE) 0170 ; 0175 ; RETURN VALUE IS CIOV RESULT IN Y REG 0180 ; 0185 ZRET = $D4 ; FR0 $D4/$D5 Return Value 0190 ; 0195 CIOV = $E456 ; CIO Vector 0200 ; 0205 IOCB = $0340 ; Base IO Control Block 0210 ICHID = IOCB+$00 ; Handler ID 0215 ICDNO = IOCB+$01 ; Device number 0220 ICCMD = IOCB+$02 ; ** CIO Command 0225 ICSTA = IOCB+$03 ; CIO Status 0230 ICBAL = IOCB+$04 ; ** Buffer address (low) 0235 ICBAH = IOCB+$05 ; ** Buffer address (high) 0240 ICPTL = IOCB+$06 ; Put char routine (low) 0245 ICPTH = IOCB+$07 ; Put char routine (high) 0250 ICBLL = IOCB+$08 ; ** Buffer length (low) 0255 ICBLH = IOCB+$09 ; ** Buffer length (high) 0260 ICAX1 = IOCB+$0A ; ** Aux Byte 1 0265 ICAX2 = IOCB+$0B ; ** Aux Byte 2 0270 ICAX3 = IOCB+$0C ; Aux Byte 3 0275 ICAX4 = IOCB+$0D ; Aux Byte 4 0280 ICAX5 = IOCB+$0E ; Aux Byte 5 0285 ICAX6 = IOCB+$0F ; Aux Byte 6 0290 ; 0295 .OPT OBJ 0300 ; 0305 *= $9000 ; Arbitrary. this is relocatable 0310 ; 0315 INIT 0320 LDY #$FF ; Make the return 0325 STY ZRET ; value -1 ($FFFF) 0330 STY ZRET+1 ; by default. 0335 ; 0340 PLA ; Get argument count 0345 BEQ BYE ; Shortcut for no args. 0350 ; 0355 TAY 0360 ; 0365 CMP #$01 ; One arg is not enough. 0370 BEQ DISPOSE 0375 ; 0380 CMP #$03 ; Three args is not supported. 0385 BEQ DISPOSE 0390 ; 0395 CMP #$07 ; More than six is not valid. 0400 BCC DO_CHANNEL ; All good. Ready to pull args. 0405 ; 0410 ; Bad arg count. Clean up for exit. 0415 ; 0420 DISPOSE ; Any number of arguments 0425 PLA 0430 PLA 0435 DEY 0440 BNE DISPOSE 0445 RTS ; Abandon ship. 0450 ; 0455 ; Pull channel and multiply times 16 0460 ; 0465 DO_CHANNEL ; Arg 1 = Channel 0470 DEY ; subtract one arg 0475 PLA ; discard high byte 0480 PLA ; Channel number 0485 CMP #$08 ; More than 7 channels 0490 BCS DISPOSE ; is invalid. 0495 ASL A ; * 2 0500 ASL A ; * 4 0505 ASL A ; * 8 0510 ASL A ; * 16 0515 TAX 0520 ; 0525 DO_ICCMD ; Arg 2 = Command 0530 PLA ; discard high byte 0535 PLA ; command byte 0540 STA ICCMD,X ; Store Command in IOCB 0545 DEY ; subtract one arg 0550 BEQ DO_CIO0555 ; 0560 DO_ICBA ; Arg 3 = Buffer Address 0565 PLA ; Address high byte 0570 STA ICBAH,X 0575 PLA ; Address low byte 0580 STA ICBAL,X 0585 DEY ; subtract one arg 0590 ; 0595 DO_ICBL ; Arg 4 = Buffer Length 0600 PLA ; Length high byte 0605 STA ICBLH,X 0610 PLA ; Length low byte 0615 STA ICBLL,X 0620 DEY ; subtract one arg 0625 BEQ DO_CIO 0630 ; 0635 DO_ICAX1 ; Arg 5 = Aux Byte 1 0640 PLA ; discard high byte 0645 PLA ; Aux byte 0650 STA ICAX1,X ; Store AUX1 in IOCB 0655 DEY ; subtract one arg 0660 BEQ DO_CIO 0665 ; 0670 DO_ICAX2 ; Arg 6 = Aux Byte 2 0675 PLA ; discard high byte 0680 PLA ; Aux byte 0685 STA ICAX2,X ; Store AUX2 in IOCB 0690 DEY ; This should be zero args now... 0695 ; 0700 DO_CIO ; IOCB is set, now execute... 0705 STY ZRET ; Clear return value low 0710 STY ZRET+1 ; and high byte. 0715 JSR CIOV ; Engage, Mr Crusher. 0720 BPL BYE ; No error 0725 STY ZRET ; Copy Y to return value 0730 ; 0735 BYE 0740 RTS 0745 ; 0750 .END The initialization is similar to prior utilities. It begins by setting the return value to a known value ($FFFF) that cannot be returned by a successful exit. Then it pulls the argument count and does a series of value checks to identify any invalid number of arguments. If the code identifies an issue here it branches to cleaning the stack and then exits. One difference in the stack argument management is that this utility does not double the argument count to derive the number of bytes on the stack, because it will not be looping to copy the stack values into Page Zero. The channel handling is more involved than other arguments: 0455 ; Pull channel and multiply times 16 0460 ; 0465 DO_CHANNEL ; Arg 1 = Channel 0470 DEY ; subtract one arg 0475 PLA ; discard high byte 0480 PLA ; Channel number 0485 CMP #$08 ; More than 7 channels 0490 BCS DISPOSE ; is invalid. 0495 ASL A ; * 2 0500 ASL A ; * 4 0505 ASL A ; * 8 0510 ASL A ; * 16 0515 TAX The channel is pulled from the low byte of the argument. If the value exceeds the range of available channels, then it diverts to the stack cleanup to dispose of the remaining arguments and exits. Recall the earlier discussion about identifying the IOCB for a specific channel -- multiply the channel number times 16 and add to $340. Here the code multiplies the channel number by 16 allowing use of the value as an index to load values into the correct IOCB. The remaining arguments are handled similarly: 0525 DO_ICCMD ; Arg 2 = Command 0530 PLA ; discard high byte 0535 PLA ; command byte 0540 STA ICCMD,X ; Store Command in IOCB 0545 DEY ; subtract one arg 0550 BEQ DO_CIO The values are pulled from the stack and stored in the corresponding IOCB field. Then the argument counter is decremented. At the end of processing the arguments for command, buffer length, and ICAX1 (arguments 2, 4, and 5) the argument count is tested if it has reached zero. If this occurs then the program skips over the work for processing any subsequent arguments. Finally, it gets down to business: 0700 DO_CIO ; IOCB is set, now execute... 0705 STY ZRET ; Clear return value low 0710 STY ZRET+1 ; and high byte. 0715 JSR CIOV ; Engage, Mr Crusher. 0720 BPL BYE ; No error 0725 STY ZRET ; Copy Y to return value Recall that the Y register is used to count arguments and by the time the routine reaches this point the Y register is guaranteed to contain zero. So, this is a convenient source to clear the high byte of the return value for BASIC. Next, the code calls the CIO Vector ($E456). When the CIO routine returns the error code is in the Y register and the utility copies that value to the low byte of the return value. Let's go over a couple implications when this is used in BASIC: 1. The function accepts an absolute address and a length allowing access to any part of memory. While this is much more flexible than XIO it also means that this routine cannot directly accept a BASIC string. This routine can use a string passed by its address via ADR(). It is also up to the BASIC program to pass the correct length. LEN() is correct only when the string has defined content, so a BASIC program must fill or pad out the string to its expected length. 2. Since this is a USR() routine it is not integrated in BASIC's error handling. Therefore TRAP cannot trap any Input/Output errors. The BASIC program must check the return value of the NXIO routine or use the STATUS command to identify problems. Testing New XIO Now that we have some experience using files for binary data we're going to start with something different. The Atari BASIC program below, MAKENXIO.BAS, creates a binary file containing the machine language code for the NXIO routine. 1 REM MAKENXIO.BAS 5 REM CREATE NXIO.BIN FILE 10 OPEN #1,8,0,"H1:NXIO.BIN" 15 FOR I=1 TO 94 20 READ D:PUT #1,D 25 NEXT I 30 FOR I=95 TO 255 35 PUT #1,0 40 NEXT I 45 CLOSE #150 END 21996 REM H1:NXIO.OBJ 21997 REM SIZE = 94 21998 REM START = 36864 21999 REM END = 36957 22000 DATA 160,255,132,212,132,213,104,240 22001 DATA 84,168,201,1,240,8,201,3 22002 DATA 240,4,201,7,144,6,104,104 22003 DATA 136,208,251,96,136,104,104,201 22004 DATA 8,176,243,10,10,10,10,170 22005 DATA 104,104,157,66,3,136,240,34 22006 DATA 104,157,69,3,104,157,68,3 22007 DATA 136,104,157,73,3,104,157,72 22008 DATA 3,136,240,14,104,104,157,74 22009 DATA 3,136,240,6,104,104,157,75 22010 DATA 3,136,132,212,132,213,32,86 22011 DATA 228,16,2,132,212,96 22012 DATA 67,3,133,212,96 This will make managing the utility easier, allowing the test program (and any other BASIC program) to load the utility directly from the file without reading DATA statements. Note that the program purposely pads the output to 255 bytes, so that a BASIC program can use the (broken) XIO command to load the binary data. Next, is the test program that exercises the features of NXIO. It begins by loading the NXIO machine language routine into a string using XIO. This is acceptable for the tightly confined usage here – the program does only one operation to read a file of 255 bytes. The remaining CIO activity in the program is run by the shiny, new NXIO routine: 100 REM TSTNXIO1.BAS10 5 REM TEST THE NEW XIO USR ROUTINE 110 POKE 82,0:GRAPHICS 0 115 DIM NXIO$(255):NXIO$(255)=" " 120 NXIO=ADR(NXIO$) 125 REM READ NXIO FROM FILE 130 OPEN #1,4,0,"H1:NXIO.BIN" 135 XIO 7,#1,4,0,NXIO$ 140 CLOSE #1 145 REM 150 REM TEST THE BAD ARG EXIT 155 REM TEST BAD ARGS 0160 ? "TESTING BAD ARGUMENTS..." 165 BADARG=USR(NXIO) 170 ? "BAD ARGS 0 = ";BADARG 175 REM TEST BAD ARGS 1 180 BADARG=USR(NXIO,3) 185 ? "BAD ARGS 1 = ";BADARG 190 REM TEST BAD ARGS 3 195 BADARG=USR(NXIO,3,3,32000) 200 ? "BAD ARGS 3 = ";BADARG 205 REM TEST BAD ARGS 7 210 BADARG=USR(NXIO,3,3,32000,2,3,3,3) 215 ? "BAD ARGS 7 = ";BADARG 220 GOSUB 595 225 REM 230 REM DO CIO 3/OPEN TO SET UP 235 REM A GRAPHICS MODE AND RUN 240 REM THE 18/FILL COMMAND.2 45 REM FORCE "S:" CLOSED 250 CLOSE #6 255 REM OPEN AS GR MODE 5, NO WINDOW 260 GROPEN=USR(NXIO,6,3,ADR("S:"),2,12,5) 265 REM SAME AS EARLIER XIO FILL DEMO 270 COLOR 3 275 PLOT 70,45:DRAWTO 50,10 280 DRAWTO 30,10:POSITION 10,45 285 POKE 765,3290 XFILL=USR(NXIO,6,18) 295 GOSUB 600:REM WAIT FOR A KEY 300 REM REPORT THE RESULTS 305 GRAPHICS 0 310 ? "GROPEN = ";GROPEN 315 ? "XFILL = ";XFILL 320 GOSUB 595 325 REM 330 REM GAMES WITH BINARY FILES 335 REM LOAD THE 8 BYTE MEMORY FILE 340 DIM D$($="!!!!!!!!" 345 ? "LOADING 8 BYTE MEMORY.BIN..." 350 OPEN #1,4,0,"H1:MEMORYT0.BIN" 355 XREAD8=USR(NXIO,1,7,ADR(D$), 360 CLOSE #1 365 FOR I=1 TO 8 370 ? ASC(D$(I,I)), 375 NEXT I 380 ? "XREAD8 = ";XREAD8 385 GOSUB 595 390 REM 395 REM SAVE THE ROM CHARACTER SET 400 CR=57 344:REM ROM SET $E000 405 ? "SAVING ROM CHARACTER SET..." 410 OPEN #1,8,0,"H1:CSET.BIN" 415 XSAVE=USR(NXIO,1,11,CR,1024) 420 CLOSE #1 425 ? "XSAVE = ";XSAVE 430 GOSUB 595 435 REM 440 REM GAMES WITH THE BINARY LOAD 445 REM SETUP SCREEN FIRST... 450 GRAPHICS 0:POSITION 0,12 455 SC=PEEK(88)+256*PEEK(89) 460 FOR Y=0 TO 7 465 FOR X=0 TO 31 470 POKE SC+Y*40+X,Y*32+X 475 NEXT X 480 NEXT Y 485 ? "NORMAL CSET DISPLAYED" 490 ? "TO LOAD SOFT SET" 495 GOSUB 595 500 REM 505 REM LOAD THE SOFT SET IN 510 REM FLIPPED HALF SETS 515 CH=36864:REM SOFT SET $9000 520 POKE 756,144 525 OPEN #1,4,0,"H1:CSET.BIN" 530 CSLOAD1=USR(NXIO,1,7,CH+512,512) 535 CSLOAD2=USR(NXIO,1,7,CH,512) 540 CLOSE #1 545 ? "SWAPPED, SOFT CSET CSET DISPLAYED" 550 GOSUB 595 555 REM 560 REM THE END 565 GRAPHICS 0 570 ? "CSLOAD1 = ";CSLOAD1 575 ? "CSLOAD2 = ";CSLOAD2 580 END 585 REM 590 REM WAIT FOR A KEY 595 ? "PRESS A KEY" 600 OPEN #1,4,0,"K:" 605 POKE 764,255 610 GET #1,A 615 CLOSE #1620 RETURN The program begins by reading the machine language routine via XIO into a string 255 characters long. Yes, the routine is actually only 94 bytes long, so it wastes a bit of space. Such is life when using XIO. The first round of tests validates the argument management. There is a separate test for each bad argument possibility – 0, 1, 3, and 7 (or greater). Each failure to start results in error code 65535 from NXIO: The next round of tests uses NXIO with all the supported arguments to open a graphics mode 5 display with no text window. Then it draws a shape and uses NXIO to execute the 18/Fill command: After the fill completes press a key to continue and then the program prints the NXIO exit codes for the Graphics Open and the Fill: Press a key again and the program runs the next test which uses 7/Get Characters to read the 8-byte MEMORYT0.BIN file created earlier. After loading the file the program prints the ATASCII codes for the bytes in the string. It should report the values below, and then print the exit code from NXIO for the 8-byte read.: Press a key after this to run the next test. This will use the 11/Put Characters to save the entire 1,024 bytes of the Atari's ROM character set to a file. Then it will print the exit code from the NXIO routine for the operation: Press a key to run the next test. This will prepare the screen to demonstrate loading the character set from the file into memory. The characters are displayed in the internal order. Note the order of the characters. Press a key to continue the test. The program will use 7/Get Characters to load the first 512 bytes from the file into the second 512 bytes of the soft character set in memory, and then it loads the second set of 512 bytes from the file into the first 512 bytes of the soft character set in memory. This effectively swaps the images of the first half of the character set with the second half. Observe the “changed” order of the characters: Finally, press a key to return the display to the normal character set and the program will display the return codes from NXIO for the loading activities and then it ends: The tests all work as expected, especially all the uses of 7/Get Characters and 11/Put Characters. So, there is no problem with CIO. The problem really is that BASIC's XIO command unnecessarily manages the interface to CIO commands. Correct use of the CIO commands is so simple and the behavior XIO implements is so involved and complicated that it is difficult to consider it simply an accident. What could be the reason for XIO's bizarrely over-engineered behavior? Perhaps at the time Atari BASIC was designed there was an expectation that these I/O operations must work in increments no less and no more than 255 bytes. Perhaps a misunderstanding between OS design and BASIC design? Perhaps design requirements were in motion and it was too late to fix the behavior. Truly weird. Below is a list of the source files and test examples from the New XIO discussion available in the disk image and archive. The files are listed in the order presented during the discussion. The BASIC programs come in the tokenized BASIC format ending in .BAS. Two listings in text format are also provided: Atari ATASCII format as .LIS and unix/linux text format ending in .TLS. New XIO File List: MSAVEDAT BASIC program to PRINT eight values to a file. MSAVEDT0 BASIC program to PUT eight bytes to a file. MLOADDT0 BASIC program to GET eight bytes from a file. XIOFILL BASIC program performing the XIO fill command using device “Q:” MSAVEDT2 BASIC program using XIO for 11/Put Bytes to write a file. MSAVEDT3 BASIC program using XIO for 11/Put Bytes to write data to a file with a trailing string to identify the source of excess file data. MLOADDT BASIC program using XIO for 7/Get Bytes. MLOADDTX BASIC program using XIO for 7/Get Bytes with a trailing string to detect excess data read from the file. MSAVE512 BASIC program to generate a file containing 512 bytes. MLOAD512 BASIC program using XIO for 7/Get Bytes attempting to load 512 bytes from a file. MLOAD8 BASIC program using XIO for 7/Get Bytes attempting to load bytes from a file containing 8 bytes. NXIO.M65 Saved Mac/65 source NXIO.L65 Mac/65 source listing NXIO.T65 Mac/65 source listed to H6: (linux) NXIO.ASM Mac/65 assembly listing NXIO.TSM Mac/65 assembly listing to H6: (linux) NXIO.OBJ Mac/65 assembled machine language program (with load segments) NXIO.BIN Assembled machine language program without load segments with additional data padded to the end of the file to make it 255 bytes long. NXIO.DAT LISTed DATA statements for NXIO machine language routine. MAKENXIO BASIC program to create the BIN file with padding to 255 bytes so the file can be loaded using XIO to read the binary data. TSTNXIO1 BASIC program testing the NXIO USR() routines for various CIO commands. ZIP archive of files: NXIO_Disk.zip Tar archive of files (remove the .zip after download) NXIO_Disk.tgz.zip Game Over The movie really is over this time. Thanks for playing. Enjoy the new toys. Finally, welcome to the new world of assembly language. Try to think of new ways to accelerate and improve BASIC programs. - End - Blessed is the man who walks not in the counsel of the wicked, nor stands in the way of sinners, nor sits in the seat of scoffers; Psalm 1:1
  7. Binary File I/O (Part 1 of 2) ============================================================== Part 1 - Introduction http://atariage.com/forums/blog/576/entry-13175-part-1-of-11-simple-assembly-for-atari-basic/ Part 2 - Learn 82.7% of Assembly Language in About Three Pages http://atariage.com/forums/blog/576/entry-13176-part-2-of-11-simple-assembly-for-atari-basic/ Part 3 - The World Inside a USR() Routine http://atariage.com/forums/blog/576/entry-13177-part-3-of-11-simple-assembly-for-atari-basic/ Part 4 - Implement DPEEK() http://atariage.com/forums/blog/576/entry-13178-part-4-of-11-simple-assembly-for-atari-basic/ Part 5 - Implement DPOKE http://atariage.com/forums/blog/576/entry-13180-part-5-of-11-simple-assembly-for-atari-basic/ Part 6 - Various Bit Manipulations http://atariage.com/forums/blog/576/entry-13181-part-6-of-11-simple-assembly-for-atari-basic/ Part 7 - Convert Integer to Hex String http://atariage.com/forums/blog/576/entry-13182-part-7-of-11-simple-assembly-for-atari-basic/ Part 8 - Convert Integer to Bit String http://atariage.com/forums/blog/576/entry-13183-part-8-of-11-simple-assembly-for-atari-basic/ Part 9 - Memory Copy http://atariage.com/forums/blog/576/entry-13184-part-9-of-11-simple-assembly-for-atari-basic/ Part 10 - Binary File I/O Part 1 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13185-part-10-of-11-simple-assembly-for-atari-basic/ Part 11 - Binary File I/O Part 2 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13186-part-11-simple-assembly-for-atari-basic-the-end/ ============================================================== Sidebar: This section turned out to be considerably more difficult and time consuming to write than anticipated. No two sources agree completely on this subject. The only consistency I found is the list of CIO and XIO command numbers. Everything else encountered documents this subject with varying amounts of accuracy. Descriptions of the CIO and XIO commands sometimes differ in the just names, but in ways that imply different expectations for results. Detailed descriptions of the commands vary from the strangely incomplete to being outright wrong. One guide for machine language on this subject described CIO features using the BASIC XIO limitations. Another tutorial declared that only 155 bytes could be read in one operation. In the end it took writing test programs in BASIC and Assembly to understand precisely how XIO commands work vs how the corresponding CIO commands actually do work. If anyone cares, the stuff that worked is derived from reading De Re Atari (50%, which was mostly correct), Compute!'s Mapping the Atari (20% and it has a couple mistakes), Atari's BASIC Reference Manual (15% which was painful and incomplete), and rest from several 6502 programming manuals that were altogether horriffic. Stating that programs work with data would be borderline silly. Everything about programs is about working with data – they calculate data, manipulate data, and copy data. A fundamental concern of programming is how to introduce data to the program and get it back out. Many programs have their data built into them or receive data by computing values, reading from storage, or by other input/output device or communications. A file contains data. A serial port sends and receives data. A joystick provides data. Numbers are data, text is data, graphic information is data. Data, Data, Data. Atari BASIC programs have access to several data acquisition methods. Data may be stored in a program using DATA statements. Data may be read from a file, or from user input. Although all data is ultimately represented as bytes in memory, BASIC languages provide a higher abstraction where the data it accepts is usually expressed as text and numbers, and even in the case of number values the input and output data is expressed as the text equivalent of the number value. This means data presented to BASIC is typically not in its final, usable form for the computer. The BASIC language reads the text data then converts it into a format the computer understands. Although an Atari BASIC program can contain its own data, it cannot have the data built into it in a form that is immediately usable. For instance, variables and array elements must be specifically initialized. The program must assign the values as it runs. There is not even a default behavior to clear numeric array values to zero. Data contained in DATA statements is not immediately usable by the Atari's unique features. The Atari's custom hardware features often use blocks of binary data – Graphics data, character set data, Player/Missile images, etc. The Atari BASIC DATA statement cannot present arbitrary binary data. It can only present decimal numbers and strings with limitations on the characters that can be included in the string. Like most BASIC languages, Atari BASIC has little provision for dealing with data in the computer's terms other than PEEK and POKE. Most Atari BASIC programs creating data in memory for Atari graphics features will read DATA statements – the text equivalent of number values – and then POKE those values as bytes into memory. This consumes a lot of storage space in the BASIC program. The byte value 192 in a DATA statement is presented as the text characters “1”, “9”, and “2” and then if there is another value the comma separator between the values also occupies memory. This one value to POKE into one byte of memory requires four supporting bytes in DATA. And then after the program stores the value in memory the DATA statement continues to occupy memory, wasting space. Wasted space means reduced space for code and reduced features in large programs. In addition to DATA's memory space issue the other problem with reading DATA statements is BASIC's slow execution time. BASIC must loop for every data value; reading the data, storing it into memory, and then reading more data. Any significant amount of data provides a long, boring wait for the user. Many BASIC games share this “feature” of making the user wait for a long program initialization. The second test program for the BITS utilities illustrates this problem. The time to store several machine language utilities totaling only about 300 bytes in memory was long enough that the program's loading section was modified to output helpful progress messages to assure the user the program had not crashed. Now consider that one complete character set is 1,024 bytes, and a complicated program may need to setup several thousand bytes of data. Assembly language and some compiled languages do not have these same issues with data storage space. These languages can declare where data will occupy memory and define the initial values. This is saved with the assembled/compiled machine language program, so the same mechanism that loads the machine language program into memory also establishes the organized, initialized data in memory. So, what to do about BASIC's problems? Eliminating the space-wasting behavior means eliminating DATA statements filled with numbers, or somehow optimizing the contents. Strings could be used to represent a lot of data. One character of a string is conveniently one byte of information. But strings still have a few problems: ATASCII characters are not always the same as binary byte values, so some translation is needed. Representing cursor control characters can be problematic when a program is LIST'ed and then ENTER'ed. There are two characters that cannot be expressed at all in a quoted string – the ATASCII End of Line character and the quote character itself. If the data includes either of these unpresentable characters then the string as DATA must replace them with a different value that can be included, and then after assigning the string it must go back and insert the problem characters into their correct positions in the string. This means adding more data to identify the locations of these special characters. Furthermore, the DATA statements filled with strings still occupy memory after the data is assigned to a string variable, though the waste factor for string characters is closer to 1:1 rather than roughly 4:1 for numeric (byte) data. If a BASIC program completely eliminates the DATA statements then where else can a program get its data? Here is an idea – This program loads values into memory from DATA: 10 RESTORE 50 20 FOR I = 0 TO 7 30 READ D:POKE 1536+I,D 40 NEXT I 50 DATA 2,34,27,155,132,130,129,128 That program above has the same end result as this program loading values into memory from a file: 10 OPEN #3,4,0,"D1:MEMORY.BIN" 20 FOR I = 0 TO 7 30 INPUT #1,D:POKE 1536+I,D 40 NEXT I 50 CLOSE #1 The difference is that the file reading method leaves behind no redundant DATA occupying valuable code and memory space (aside from the code to load the data.) Whether reading eight bytes or 800 bytes the amount of code to read from a file is the same. So, how does the data get into the file? A little one-time work is required to write the data into the file. Here is the same original code, but instead of POKE'ing the data into memory it writes the data out to the file. Then, the original program can be changed to read the data from the file and POKE that into memory, and so eliminate the DATA statements. 1 REM MSAVEDAT.BAS 5 REM SAVE FILE FROM DATA 10 RESTORE 50 15 OPEN #1,8,0,"H1:MEMORY28.BIN" 20 FOR I=0 TO 7 30 READ D:? #1;D 40 NEXT I 45 CLOSE #1 50 DATA 2,34,27,155,132,130,129,128 There is a question that is not obvious at this point, but will be wedged in here now, because the answer determines the way the code should write data to and read data from the file, and ultimately the direction of an assembly language utility. The question is, “What data is in the file?” Most of the time I work with the Atari800 or Atari++ emulators in Linux to extend the lifespan of the real 8-bit hardware, so here I will detour into Linux for the benefit of tools that show exactly what is in the data file. First of all, the file itself containing the data written for the 8 bytes: -rw-rw-rw- 1 kenjen users 28 Feb 24 18:15 MEMORY28.BIN This file is intended to contain data for 8 bytes, so then why does the directory listing report the file contains 28 bytes? A hex dump utility shows the file contains the following information: 0000 32 9b 33 34 9b 32 37 9b 31 35 35 9b 31 33 32 9b 2.34.27. 155.132. 0010 31 33 30 9b 31 32 39 9b 31 32 38 9b 130.129. 128. The right side of the hex dump provides the explanation. The file contains the text representation (ASCII/ATASCII) of the numeric values. The byte $9B is the ATASCII CHR$(155), the Atari's End Of Line character, which appears after each value. Recall that BASIC prefers to read and write data using text representation of values. The example program uses PRINT (or ?) and INPUT which quietly translate between an internal representation and the text representation. This is BASIC's data abstraction versus the programmer's desire to have BASIC do what is meant, not what is written. The program's (poorly expressed) intent is to store and read bytes of memory. However, BASIC treats the numbers as bytes only within the context of PEEK and POKE instructions. So, then how to get data into the file that is just discrete byte values? Single characters are bytes, so using the CHR$() function will output numeric values as single characters (aka bytes), so that's potentially workable for output. But, then how about reading the bytes? A program using INPUT will still read the file contents as a string which means it will try to read bytes until it reaches an End of Line ($9B) character. So, this is also not a workable solution. Atari BASIC provides a method to write and read a file one byte at a time with the commands PUT and GET. The same example program using PUT instead of PRINT (or ?): 1 REM MSAVEDT0.BAS 5 REM PUT BYTES TO A FILE 10 RESTORE 50 15 OPEN #1,8,0,"H1:MEMORYT0.BIN" 20 FOR I=0 TO 7 30 READ D:PUT #1,D 40 NEXT I 45 CLOSE #1 50 DATA 2,34,27,155,132,130,129,128 The program above results in this file only 8 bytes long: -rw-rw-rw- 1 kenjen users 8 Feb 24 18:52 MEMORYT0.BIN and that file contains the eight values as individual bytes (Linux Hex dump): 0000 02 22 1b 9b 84 82 81 80 ."...... This program uses GET instead of INPUT to retrieve the data to store in memory: 1 REM MLOADDT0.BAS 5 REM GET BYTES FROM FILE 10 OPEN #3,4,0,"H1:MEMORYT0.BIN" 20 FOR I = 0 TO 7 30 GET #1,D:POKE 1536+I,D 40 NEXT I 50 CLOSE #1 So, now we know how using data files can save valuable memory space in BASIC, and how to optimize the file content to the actual bytes as they would be stored in memory. However, a fundamental problem with BASIC remains – the program must read the file data byte by byte during a loop, and BASIC's slow execution speed will turn any significant amount of data into a long and inconvenient wait. In fact, doing device I/O byte by byte in BASIC is slower than reading from DATA statements in memory, so this memory saving solution penalizes the program with even slower data loading. If only there was some kind of machine language solution that could read the bytes from a file as fast as possible. What to do, Toto?!? OSS's BASIC XL provides a precedent with the Bput and Bget commands that write and read arbitrary length blocks of memory directly to and from a file as fast as the device can transfer bytes which is far faster than BASIC's ability to loop for individual bytes. How does BASIC XL manage this? Is it simply reading individual characters in a loop at machine language speed? Or is it doing another trick? It turns out that bulk input and output of bytes is a feature provided by the Atari OS's Centralized I/O (CIO) routines, but the problem is that Atari BASIC does not support all the commands that CIO provides. Gaming Atari's CIO (or not) Many systems of the 8-bit era require the programmer use unique calls to read and write information to each kind of device. Each act of reading a disk file, a cassette file, and user input typed from a keyboard may require calling different entry points in the OS. Likewise, writing to a disk file, a cassette, the screen, or a printer may also require calling different OS routines. Even using two disk drives could require different OS calls. Input/Output programming on these systems can be tedious affairs of device-specific coding for every possible input/output option which deters programmers from even providing choices to users. But the Atari system is different. The Atari includes a standard, modular, reusable input/output model. Simply put, the programmer fills out a common data structure describing the input/output operation and makes a call to one address in the OS. This is a high level abstraction for device input/output. There are no sectors to consider, no serial communications to worry about, no fixed buffers in the system. Everything is a stream of data in or out, read or written on demand. In a very general way this is similar to the unix world's “everything-is-a-file” philosophy. Changing disk file output to screen or printer output requires only a different device/file name. The setup and the call to the OS are the same for all. Considering the Atari's Central I/O (CIO) was written in the late 70s this is nearly magical, and very under-appreciated behavior in an 8-bit computer. Atari CIO The Atari CIO defines a basic set of commands that every handler must be prepared to accept. (Listed in the chart below.) This doesn't necessarily mean every device must do every I/O command. A keyboard cannot do output, and a printer cannot do input. However, the device handler is responsible for sanely managing commands and safely replying that an incompatible command request is not implemented. Command Command Number Description Open 3 Same as BASIC's OPEN command Get Text Record 5 Similar to BASIC's INPUT command Get Characters (Bytes) 7 Similar to BASIC's GET command Put Text Record 9 Similar to BASIC's PRINT command Put Characters (Bytes) 11 Similar to BASIC's PUT command Close 12 Same as BASIC's CLOSE command Status 13 Same as BASIC's STATUS command Command numbers above this range are handler-specific operations. For example, commands 17 and 18 are specific to the screen device (“S:”) to perform graphics DRAWTO and Fill, respectively. Commands from 32 to 38 (and higher) perform various DOS file management functions for the “?” device. Per the list above, everything provided by the base CIO commands appear to correspond to a BASIC language I/O command. Well, almost, but not quite – “Similar” is not the same as “Same”. There is a bit of a disconnect between how BASIC uses these CIO actions, and what the CIO actions can really accomplish. Atari BASIC does not actually use the 5/Get Text Record and 9/Put Text Record. These commands read and write a stream of text characters ending with the Atari End Of Line character which is the Atari OS/CIO's general definition of “Text Record”. Atari BASIC's PRINT and INPUT behaviors are more complicated than the “Text Record” model, because BASIC I/O activity occurs incrementally rather than in a complete record. INPUT can handle multiple variables in one line. PRINT outputs variables and strings as BASIC interprets the values. PRINT also uses semicolons to suppress the end of line, and commas cause tabs/columnar output which are not inherent abilities in the CIO 9/Put Text Record command. So, BASIC is not using the Text Record I/O commands and is managing the I/O by other means. Additionally, notice that the titles for command 7/Get Characters and command 11/Put Characters do not exactly match how BASIC uses those commands. Both commands move bytes – stress the plural: b-y-t-e-S. However, Atari BASIC uses these CIO commands in the most minimal manner to serve as PUT and GET which move only one byte. Since we're looking for a way to read or write an arbitrary number of bytes (plural) these two CIO commands appear to be exactly what we need. The CIO command list above comes from Atari's “BASIC REFERENCE MANUAL” discussion of BASIC's XIO command. This suggests that XIO is capable of exercising these commands. If this is true, then the XIO command could be used to run these CIO operations as CIO intended. That's the theory. The IOCB First, let's learn how CIO operations work. The next section will examine how the XIO command relates to the CIO operations. Earlier it was stated that the programmer fills out a common data structure describing the input/output operation. This data structure is called the Input/Output Control Block (or IOCB). Each of the eight input/output channels has a 16-byte, IOCB dedicated to it at fixed locations in memory starting at $340/832(dec) for channel 0, $350 for channel 1, and so on up to $3B0 for channel 7. So, it is easy to find the IOCB for a channel. Multiply the channel number by 16 and add that to $340. The IOCB tracks the state of I/O operations to the channel. Though the IOCB is 16 bytes long the programmer need only interact with a few of the bytes. Some of the bytes are maintained by the OS, and some are not typically used at all except for special device commands. The values are referred to by their offset from the start of the IOCB: ICCMD: IOCB + $2 This is the CIO command discussed above. ICSTA: IOCB + $3 This is the last error/status of the previously completed CIO command. ICBAL/ICBAH: IOCB + $4 and + $5 This is the 16-bit address (low byte and high byte) of the input or output buffer here. In the case of 3/Open and CIO commands for DOS operations on disk files this buffer is the address of the string for the device/file name. ICBLL/ICBLH: IOCB + $8 and + $9 This is the 16-bit length (low byte, high byte) of the data here. In the case of read and write operations (5, 7, 9, or 11) this is the number of bytes to read in or write out from the buffer. CIO will update this value indicating the number of bytes actually transferred. In the case of 3/Open and commands for DOS operations on disk files this is the length of the device/file name. ICAX1: IOCB + $A Auxiliary byte for the handler. This commonly indicates the Mode for the 3/Open command. CIO will maintain this value here. Meaning of Mode values: For files combine these values for the Mode: 8 - write 4 - read 1 - append For the disk/DOS specifically: 2 - open the directory. For the “S:” device additional values direct how the OS opens graphics mode displays: 16 - Create the text window. 32 - Do not clear screen memory. Finally, For the “E:” handler the value 1 added to the open state (12 + 1 = 13) enables forced read Mode from the screen as if the Return key were held down. ICAX2: IOCB + $B Auxiliary byte for the handler. For the “S:” handler this specifies the Graphics mode to open. (The GRAPHICS numbers that BASIC uses, not ANTIC modes.) For the “?” device value 0 is normal inter-record gaps, and 128 is the faster, short inter-record gaps. Other serial devices may use specific values here. The programmer need not be concerned with much more in most situations. The other bytes in the IOCB are either the responsibility of CIO, or only used for specific device functions. For the sake of reference: ICHID: IOCB + $0 Set by the OS to the index into the handler table when the channel is currently open. If the channel is closed the default value is 255/$FF. ICDNO: IOCB + $1 Set by the OS to the device number when multiple devices of the same kind are in use - e.g. 1 for “D1:”, 2 for “D2:”. ICPTL/ICPTH: IOCB + $6 and + $7 CIO populates this with the jump address for the handler's single-character PUT routine. Atari BASIC uses this as a shortcut to output characters. ICAX3, ICAX4, ICAX5, ICAX6: IOCB + $C through + $F Optional auxiliary bytes for the handler. NOTE and POINT DOS operations use these locations. Atari BASIC's XIO BASIC's XIO command provides a generic interface to access CIO functions that BASIC does not directly implement with a built-in command. The diagram below shows how the XIO values relate to the CIO's IOCB fields: XIO cmdno, #aexp, aexp1, aexp2, filespec ↓ ↓ ↓ ↓ ↓ CIO ICCMD Channel number ICAX1 ICAX2 ICBAL + ICBAH, ICBLL + ICBLH The XIO arguments conveniently match the list of CIO's IOCB values that a programmer should manage. This should mean the XIO command can substitute for any other CIO I/O command (in theory). An important distinction concerning XIO behavior vs the usual CIO expectations is that the 3/Open command uses ICAX1 and ICAX2 values where most other CIO commands don't need to specify those values. Depending on the device and the CIO command the original values set by the 3/Open could be significant and should not be disturbed. However, XIO accepts ICAX1 and ICAX2 values on every command which potentially could allow a later command to overwrite the values set by 3/Open. If ICAX1 and ICAX2 are important for later commands then for safety's sake they should be remembered and restated on subsequent use of XIO. A variety of documentation describing XIO share a common mistake by describing the last argument as always a filespec (the device identifier, and filename if applicable). In reality, the last argument simply provides the data buffer and length for the CIO command. This buffer has variable purpose. It is expected to contain the filespec for 3/Open and DOS-specific file manipulation commands. For most other commands this argument provides the input or output buffer for data. Atari BASIC can use an explicit string or a DIMensioned string for the buffer argument to XIO. The contents of this string does not even matter for some commands – for example, the 18/Fill and 17/Drawto CIO commands for the “S:” device do not use the buffer, so they do not care what value is passed. Below is an example of XIO performing the 18/Fill command for the non-existent device “Q:”. This works, because the real device identifier, “S:”, is only needed when the screen device is opened and the 18/Fill command does not use the buffer. 1 REM XIOFILL.BAS 5 REM DO FILL ON MYTHICAL Q: DEVICE 10 GRAPHICS 5+16 20 COLOR 330 PLOT 70,45:DRAWTO 50,1040 DRAWTO 30,10:POSITION 10,45 50 POKE 765,3 60 XIO 18,#6,0,0,"Q:" 70 GOTO 70 XIO and CIO 11/Put Bytes Assuming the XIO command works as advertized it should give access to the CIO commands 7/Get Bytes and 11/Put Bytes to manage a stream of bytes of arbitrary length. Below is a program using XIO to write a binary file using the 11/Put Bytes command. For the sake of demonstrating the global nature of XIO the other BASIC commands to OPEN and CLOSE the file are also replaced by XIO performing CIO commands 3/Open and 12/Close: 1 REM MSAVEDT2.BAS 5 REM SAVE FILE FROM DATA BY XIO 10 RESTORE 15 DIM D$(8) 20 FOR I=1 TO 8 25 READ D:D$(I)=CHR$(D) 30 NEXT I 35 ICAX1=8:ICAX2=0 39 REM OPEN 40 XIO 3,#1,ICAX1,ICAX2,"H1:MEMORY2.BIN" 44 REM PUT BYTES 45 XIO 11,#1,ICAX1,ICAX2,D$ 49 REM CLOSE 50 XIO 12,#1,ICAX1,ICAX2,"H:" 60 DATA 2,34,27,155,132,130,129,128 Hey! That seemed to work! So, lets take a look at the MEMORY2.BIN file... -rw-r--r-- 1 kenjen users 255 Feb 27 16:58 MEMORY2.BIN What on earth?!? The string is 8 bytes, but the file contains 255 bytes? How weird. Let's see what hexdump in linux says about the contents... 0000 02 22 1b 9b 84 82 81 80 9b 08 00 00 00 00 40 01 ."...... ......@. 0010 00 00 00 00 81 14 00 04 00 19 00 04 04 00 00 00 ........ ........ 0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........ 0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........ . . . 00e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........ 00f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ....... The 8 bytes that the program intended to write are there at the beginning of the file where they should be, so that's one good thing. But, the rest of the file appears to be junk out to the length of 255 bytes. It looks like the write started with the designated data string and then kept on going through whatever memory followed the string. Make the following additions to test this theory... 17 DIM E$(26) 18 E$="This should not be output." After running the program again and putting the file through hexdump: 0000 02 22 1b 9b 84 82 81 80 9b 68 69 73 20 73 68 6f ."...... .his sho 0010 75 6c 64 20 6e 6f 74 20 62 65 20 6f 75 74 70 75 uld not be outpu 0020 74 2e 40 08 00 00 00 00 40 01 00 00 00 00 81 14 t.@..... @....... 0030 00 04 00 19 00 04 00 00 00 00 00 00 00 00 00 00 ........ ........ 0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........ 0050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........ . . . 00e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........ 00f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ....... So, that confirms it. The eight characters from D$ string were output and then followed by whatever happened to be in memory after that string to a total length of 255 bytes. This would suggest a simple bug with the way Atari BASIC passed the output length to CIO, but there's something else in the output that tells us the problem is worse than that. The byte that follows the first eight bytes is $9b, the Atari End of Line character and which occupies the place in the output where the first character of E$ should appear. (Interesting.) That End of Line character is not part of the real data of either of the strings. This must mean that Atari BASIC intentionally output the End of Line after the D$ content as if it were performing PRINT. The odd part is that Atari BASIC accounted for this End of Line by skipping the next actual byte in memory which is the first character of E$. Then Atari BASIC figured out how many bytes remained until it reached 255 bytes and directed output of those bytes from memory. In the Olympics of buggy behavior this is a Track and Field gold medalist. What could have possessed them to do this? Perhaps this could be salvageable. The good part is the intended bytes were output intact at the beginning of the file. If the behavior is simply that Atari BASIC pads additional content, then this is a usable and (barely) tolerable situation for binary file output. So, let's see what happens with more data – such as half a kilobyte simulating an Antic mode 6 or 7 character set: 1 REM MSAVEDT3.BAS 5 REM SAVE LARGE FILE WITH XIO 15 DIM D$(512) 20 FOR I=1 TO 497 STEP 16 25 D$(I)="0123456789ABCDEF" 30 NEXT I 35 ICAX1=8:ICAX2=0 39 REM OPEN 40 XIO 3,#1,ICAX1,ICAX2,"H1:MEMORY3.BIN" 44 REM PUT BYTES 45 XIO 11,#1,ICAX1,ICAX2,D$ 49 REM CLOSE 50 XIO 12,#1,ICAX1,ICAX2,"H:" And after running the program the actual file length is... disappointment again... -rw-r--r-- 1 kenjen users 255 Feb 27 22:14 MEMORY3.BIN One point of good news is the hexdump utility shows the file does contain only the real bytes of the intended string up to 255 characters and that the last byte in the file is not substituted with the End Of Line: 0000 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 01234567 89ABCDEF 0010 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 01234567 89ABCDEF 0020 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 01234567 89ABCDEF . . . 00e0 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 01234567 89ABCDEF 00f0 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 01234567 89ABCDE That nails the coffin shut. Atari BASIC's use of the CIO 11/Put Bytes command is almost completely broken. For some reason Atari BASIC XIO believes the 11/Put Bytes must output a 255 byte, fixed-length block and Atari BASIC doctors the I/O activity to meet this expectation. Next we will look at CIO's 7/Get Bytes command and see if Atari BASIC's support is also broken for this command. XIO and CIO 7/Get Bytes First, let's verify that 7/Get Bytes can at least read the first 8 bytes from the 255 byte binary file: 1 REM MLOADDT.BAS 5 REM READ FILE USING XIO 10 DIM D$(8):D$(8)=" " 15 ICAX1=4:ICAX2=0 19 REM OPEN 20 XIO 3,#1,ICAX1,ICAX2,"H1:MEMORY2.BIN" 24 REM GET BYTES 25 XIO 7,#1,ICAX1,ICAX2,D$ 29 REM CLOSE 30 XIO 12,#1,ICAX1,ICAX2,"H:" 35 FOR I=1 TO 8 40 ? ASC(D$(I,I)), 45 NEXT I:? When it runs BASIC reports: 2 34 27 155 132 130 129 128 It looks like it is reading the data into the string correctly. But, is there anything else going on? Remember Atari BASIC thinks the write has to output 255 bytes. Maybe it is also doing something strange during the read. Lets see if it is reading more than directed: 1 REM MLOADDTX.BAS 5 REM READ FILE USING XIO 10 DIM D$(8):D$(8)=" " 11 DIM E$(255):E$="!":E$(255)="!":E$(2)=E$ 15 ICAX1=4:ICAX2=0 19 REM OPEN 20 XIO 3,#1,ICAX1,ICAX2,"H1:MEMORY2.BIN" 24 REM GET BYTES 25 XIO 7,#1,ICAX1,ICAX2,D$ 29 REM CLOSE 30 XIO 12,#1,ICAX1,ICAX2,"H:" 35 FOR I=1 TO 8 40 ? ASC(D$(I,I)), 45 NEXT I:? 50 ? E$ This is basically the same program with the addition of a large E$ declared and populated entirely with exclamation points, "!". If Atari BASIC reads only the 8 bytes it needs to populate D$ then E$ should be undisturbed and the program will end by displaying 255 uninterrupted exclamation points. But, this is the actual result: Unfortunately, this did not do what is expected or needed. BASIC overwrote E$ with the remaining data from the file. (An interesting bit of trivia is that the first character of E$ is preserved. This character corresponds to the End Of Line that BASIC had inserted in the output.) So, now we know that the CIO 7/Get Bytes command works well enough from Atari BASIC to retrieve short sequences of binary data as long as a buffer of 255 bytes is provided. Now to figure out if it can load longer data. Given the 255 character limit when using 11/Put Bytes the next program will test that limit for reading. First, we need a file that has more than 255 bytes. Recall that the earlier attempt to write 512 bytes actually failed, so here is a (much slower) program to make the 512 byte file as intended: 1 REM MSAVE512.BAS 5 REM WRITE 512 BYTES TO FILE 10 DIM D$(16):D$="0123456789ABCDEF" 15 OPEN #1,8,0,"H1:MEM512.BIN" 20 FOR I=1 TO 32 25 FOR J=1 TO 16 30 D=ASC(D$(J)) 35 PUT #1,D 40 NEXT J 45 NEXT I 50 CLOSE #1 Next is the program to test if Atari BASIC XIO has a 255 byte limit for the 7/Get Bytes command similar to the way it limits 11/Put Bytes: 1 REM MLOAD512.BAS 5 REM READ 512 BYTES FROM FILE 10 DIM D$(512) 15 D$="!":D$(512)="!":D$(2)=D$ 20 ICAX1=4:ICAX2=0 24 REM OPEN 25 XIO 3,#1,ICAX1,ICAX2,"H1:MEM512.BIN" 29 REM GET BYTES 30 XIO 7,#1,ICAX1,ICAX2,D$ 34 REM CLOSE 35 XIO 12,#1,ICAX1,ICAX2,"H:" 40 ? D$ D$ is declared 512 bytes long and filled with exclamation points. Then it attempts to read the 512 bytes from the file into D$. If this works properly the entire contents of D$ should show the "0" to "F" pattern read from the file all the way to the end. But this is what happens, instead: No joy for Atari BASIC's XIO. The result shows the first 255 characters of D$ are populated with the values from the file and the remainder of D$ is the exclamation points set during the program initialization. But, the horror is not over. Recall the MEMORYT0.BIN file created earlier that contains only 8 bytes? This program attempts to read just those 8 bytes from the file: 1 REM MLOAD8.BAS 5 REM READ THE 8 BYTES FROM FILE 10 DIM D$(8) 15 D$="!!!!!!!!" 20 ICAX1=4:ICAX2=0 24 REM OPEN 25 XIO 3,#1,ICAX1,ICAX2,"H1:MEMORYT0.BIN" 29 REM GET BYTES 30 XIO 7,#1,ICAX1,ICAX2,D$ 34 REM CLOSE 35 XIO 12,#1,ICAX1,ICAX2,"H:" 40 ? D$ And this is what happens when the program runs: ERROR- 136 AT LINE 30 The program gets an End Of File error during the XIO performing the 7/Get Bytes request. BASIC XIO expects to read 255 bytes from the file no matter the size of the string buffer supplied, even if the buffer is defined shorter. So, the bottom line is that Atari BASIC XIO has mangled use of the CIO 7/Get Bytes command similar to the 11/Put Bytes command. Here's a summary of what we have left of these CIO command as Atari BASIC has abused them: XIO for 7/Get Bytes is usable for reading binary data from 1 to 255 bytes. If the intended data is shorter than actual file size (of 255 bytes) then Atari BASIC will continue retrieving data from the file and storing it into memory up to that 255 byte maximum length. In order to complete this activity the file must have at least 255 bytes, and in the interest of safety the string buffer accepting the data should be 255 bytes. XIO for 11/Put Bytes is usable to write 1 to 255 bytes to a file with the understanding that anything shorter than 255 bytes will be padded to the length of 255 bytes with junk. In addition, Atari BASIC's capability is further restricted, because it only performs input and output to a a string variable. Saving and loading data to any arbitrary memory location is problematic. This could be circumvented with more involved coding to manipulate a string table entry to point to a specific memory addresses before the read or write. But, again, since BASIC insists on moving 255 bytes at a time, even this method isn't a great solution. We're not going to even bother trying that. In the next episode we'll look at a machine language replacement for XIO and its test program. For my thoughts are not your thoughts, neither are your ways my ways, declares the Lord. For as the heavens are higher than the earth, so are my ways higher than your ways and my thoughts than your thoughts. Isaiah 55:8-9
  8. Memory Copy ============================================================== Part 1 - Introduction http://atariage.com/forums/blog/576/entry-13175-part-1-of-11-simple-assembly-for-atari-basic/ Part 2 - Learn 82.7% of Assembly Language in About Three Pages http://atariage.com/forums/blog/576/entry-13176-part-2-of-11-simple-assembly-for-atari-basic/ Part 3 - The World Inside a USR() Routine http://atariage.com/forums/blog/576/entry-13177-part-3-of-11-simple-assembly-for-atari-basic/ Part 4 - Implement DPEEK() http://atariage.com/forums/blog/576/entry-13178-part-4-of-11-simple-assembly-for-atari-basic/ Part 5 - Implement DPOKE http://atariage.com/forums/blog/576/entry-13180-part-5-of-11-simple-assembly-for-atari-basic/ Part 6 - Various Bit Manipulations http://atariage.com/forums/blog/576/entry-13181-part-6-of-11-simple-assembly-for-atari-basic/ Part 7 - Convert Integer to Hex String http://atariage.com/forums/blog/576/entry-13182-part-7-of-11-simple-assembly-for-atari-basic/ Part 8 - Convert Integer to Bit String http://atariage.com/forums/blog/576/entry-13183-part-8-of-11-simple-assembly-for-atari-basic/ Part 9 - Memory Copy http://atariage.com/forums/blog/576/entry-13184-part-9-of-11-simple-assembly-for-atari-basic/ Part 10 - Binary File I/O Part 1 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13185-part-10-of-11-simple-assembly-for-atari-basic/ Part 11 - Binary File I/O Part 2 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13186-part-11-simple-assembly-for-atari-basic-the-end/ ============================================================== A number of features on the Atari benefit from fast memory copies. High speed data copying provides convenience to the user by reducing wait times for actions that would use slow, time-consuming BASIC loops. Copying a character set (1024 bytes) is a good example. High speed memory moves also enable use of Atari features that are not otherwise possible due to BASIC's speed. For example, vertical movement and animation of Player/Missile graphics in BASIC is not realistic, but a memory move function makes P/M graphics animation practical in an Atari BASIC program. Though Atari BASIC lacks a general purpose memory move there is a hack around this problem that exploits the way Atari BASIC manages strings. As simply as possible: A BASIC program may dig through the variable control structures and change the starting address of a string variable, effectively overlaying the string on any memory possible: screen memory, player/missile memory, color registers, character sets, hardware addresses, etc. String assignments then cause memory to be assigned or copied at near machine language speeds. The copy is done in ascending order and there is no consideration for source and destination memory (strings) that overlap. However, this string abuse method is a subject for an entirely different discussion and is not what will be done here. The subject of memory moves on the 6502 includes many considerations and is the inspiration for numerous discussions, arguments, and holy wars between programmers around the subject of speed, efficiency, optimization, and the odors emanating from the grandmothers of those who disagree with one's obvious and indisputable common sense. In short, although copying from A to B seems like a simple subject, there are a many choices and different methods to arrange a 6502 algorithm to copy memory. Topics that make a difference in code: How many bytes are being copied? More or less than 128 bytes? More or less than 256 bytes? More or less than 32K bytes? Does the code need to consider whether or not the source and destination memory overlap? Should the memory be copied in ascending or descending order? Is speed important? Should loops be unrolled into explicit code? Is the size of the code more important than speed? All of the above can start a group of programmers on a never-ending flame war. For the sake of defusing arguments I will admit that the result created here will not be everyone's idea of best, fastest and most efficient. We will follow the rule, “moderation in all things.” Well, more of a guideline than a rule. Let's say that we are copying four bytes from a source to a destination. The fastest way to copy is to do it directly and explicitly: LDA SOURCE STA DEST LDA SOURCE+1 STA DEST+1 LDA SOURCE+2 STA DEST+2 LDA SOURCE+3 STA DEST+3 That's about as fast as it gets on the 6502. Depending on whether or not source or destination is in Page Zero, this code uses about 4 to 6 bytes worth of instructions (total of 16 to 24 bytes just for this example!) and 6 to 8 cycles of CPU time for each byte it moves. That's all the good about it. Now the reality check: The code is not general purpose. When source and destination change then another block of code must be created. This method makes the assembly source code for a memory move of any substantial size very wordy, not to mention the tedious typing for the programmer. An explicit copy like this is done when performance is the ultimate goal. The next level of optimization is a loop to move a range of bytes: LDX #$00 LOOP LDA SOURCE,X STA DEST,X INX CPX #$04 BNE LOOP This code uses an index to copy bytes in ascending order and makes a comparison for the number of bytes. The code occupies 9 to 11 bytes (without the LDX initialization), roughly half the size of the explicit method. The size efficiency becomes greater the more bytes are copied. This code can copy 4 bytes, 10 bytes, or 100 bytes and remains the same size, while the earlier explicit method expands with every byte. However, this code uses 15 to 17 CPU cycles (not including the LDX initialization which occurs only once) for every byte it copies which is more than twice as long as the explicit copy. Additionally, this still uses absolute addresses and is not a general purpose, reusable routine. Here is a little optimization on this method: LDX #$03 LOOP LDA SOURCE,X STA DEST,X DEX BPL LOOP This version uses an index to copy bytes in descending order and eliminates the compare instruction by relying on the CPU negative flag changing when the index decrements from $00 to $FF. This reduces the code size to 7 to 9 bytes without the LDX initialization. Execution time (without the LDX) is reduced to 13 to 15 cycles per byte copied – just about twice as long at the explicit method. Again, like all other examples this uses absolute addresses and so is not a general purpose, reusable routine. A general purpose routine must use indirection instructions allowing the source and destination addresses to be different each time the program uses the routine. This means Page Zero locations must be initialized for source and destination:. LDA # <SOURCE STA ZSOURCE LDA # >SOURCE+1 STA ZSOURCE+1 LDA # <DEST STA ZDEST LDA # >DEST+1 STA ZDEST+1 ; LDY #$03 LOOP LDA (ZSOURCE),Y STA (ZDEST),Y DEY BPL LOOP This version is not so short. The initialization (executed once) is 18 bytes long. While the actual looping section is only 7 bytes it executes in 15 to 17 cycles due to the indirection. The advantage here is that the looping section is reusable. Another consideration of the indexed, looping methods is that they can copy a limited number of bytes. The maximum number of bytes a loop can copy using the X or Y register as an index is 256 bytes ($00 to $FF). The method of comparison or detection of the end of the copy loop can also limit the number of bytes. An earlier example copies in descending order uses BPL to detect the index wrapping around from $00, a positive value, to $FF, a negative value. This means the branching evaluation cannot encounter any other negative value which limits this code 129 bytes. 129? not 128? Why? LDX #$80 ; Start at +128 LOOP LDA SOURCE,X STA DEST,X DEX ; From here X as $7F descending BPL LOOP ; to $00 is valid It is possible to copy a specific number of bytes less than 256 in ascending order without using a comparison instruction, but, this is less obvious. This code example below alters the starting address to compensate for the starting index, so that the branch decision can rely on the CPU flag change when the index reaches zero: ; $00 - $04 = $FC LDX #$FC LOOP LDA SOURCE-$FC,X STA DEST-$FC,X INX ; Continues for $FD, $FE, $FF BNE LOOP ; and quits at index $00 So, then what do do when more there are more than 256 bytes to copy? In the simple loop examples just add another loop that copies more bytes with different source and destination addresses. But, when there is a lot of memory to copy this quickly becomes redundant. The advantage of using the indirection method is that the source and destination addresses in Page Zero can be easily modified and used to repeat the loop again. The example below copies 1024 bytes: LDX #$04 ; Number of 256 byte pages LDY #$00 LOOP LDA (ZSOURCE),Y STA (ZDEST),Y INY BNE LOOP ; Copy a page INC ZSOURCE+1 ; Increment addresses' INC ZDEST+1 ; high bytes DEX BNE LOOP ; Do another page The looping part is 14 bytes long. It can copy any number of 256 byte pages just by changing the X register. This is a practical routine for the Atari which has character sets 512 or 1024 bytes long. Player/missile graphics may use memory maps of 256 bytes. More generally, aligning any data to fit within the range of an index register allows reasonably compact code like this. The next problem is how to copy any number of bytes. Simply change perspective and think of the problem in two parts. First, if the number of bytes is 256 or greater then the previous page copy shown above can take care of all the whole, 256-byte pages. The high byte of the size provides the number of whole 256 byte pages. That leaves the size's low byte which specifies the remaining zero to 255 bytes. This second part just needs a slightly different byte copying loop that will stop early at a specific count. Assuming the source, destination, and size are all set into designated page 0 locations, then the relocatable, re-usable code could look something like this: MEMMOVE in Mac/65 Assembler Code 0100 ; MEMMOVE.M65 0105 ; 0110 ; GENERAL MEMORY MOVE 0115 ; 0120 ; GENERIC MEMORY MOVE FROM 0125 ; SOURCE TO DESTINATION 0130 ; ASCENDING. 0135 ; 0140 ; USR 3 ARGUMENTS: 0145 ; SOURCE == FROM ADDRESS 0150 ; DEST == TO ADDRESS 0155 ; SIZE == NUMBER OF BYTES 0160 ; 0165 ; RETURN VALUE IS BYTES COPIED. 0170 ; 0175 ZRET = $D4 ; FR0 $D4/$D5 Return Value 0180 ZARGS = $D5 ; $D6-1 for arg Pulldown loop 0185 ZSIZE = $D6 ; FR0 $D6/$D7 Size 0190 ZDEST = $D8 ; FR1 $D8/$D9 Destination 0195 ZSOURCE = $DA ; FR1 $DA/$DB Source 0200 ; 0205 .OPT OBJ 0210 ; 0215 *= $9200 ; Arbitrary. this is relocatable 0220 ; 0225 INIT 0230 LDY #$00 ; Make the return 0235 STY ZRET ; value clear to 0 0240 STY ZRET+1 ; by default. 0245 PLA ; Get argument count 0250 BEQ BYE ; Shortcut for no args 0255 ASL A ; Now number of bytes 0260 TAY 0265 CMP #$06 ; Source, Dest, Size 0270 BEQ PULLDOWN 0275 ; 0280 ; Bad arg count. Clean up for exit. 0285 ; 0290 DISPOSE ; Any number of arguments 0295 PLA 0300 DEY 0305 BNE DISPOSE 0310 RTS ; Abandon ship. 0315 ; 0320 ; Pull args into Page Zero. 0325 ; This code works the same 0330 ; for 1, 4, 8... arguments. 0335 ; 0340 PULLDOWN ; arguments in Y 0345 PLA 0350 STA ZARGS,Y 0355 DEY 0360 BNE PULLDOWN 0365 ; 0370 ; Since we're good to start the 0375 ; copy, then set the return 0380 ; value to the size. 0385 ; 0390 LDA ZSIZE+1 0395 STA ZRET+1 0400 LDA ZSIZE 0405 STA ZRET 0410 ; 0415 ; Moving full 256 byte pages or not? 0420 ; 0425 LDY #0 0430 LDX ZSIZE+1 ; Number of pages is 0435 BEQ MOVEPARTIAL ; Zero so, try partial. 0440 ; 0445 ; Copy full index range of 256 bytes. 0450 ; 0455 MOVEPAGE 0460 LDA (ZSOURCE),Y 0465 STA (ZDEST),Y 0470 INY 0475 BNE MOVEPAGE ; Y rolled $FF to $00 0480 INC ZSOURCE+1 ; Next page src 0485 INC ZDEST+1 ; next page dst 0490 DEX ; this page done 0495 BNE MOVEPAGE ; Non-zero means more 0500 ; 0505 ; A partial page remains? 0510 ; 0515 MOVEPARTIAL 0520 LDX ZSIZE ; Low byte remainder. 0525 BEQ BYE ; Zero, exit 0530 ; 0535 MOVEMEM 0540 LDA (ZSOURCE),Y 0545 STA (ZDEST),Y 0550 INY ; Copy ascending. 0555 DEX ; and subtract count. 0560 BNE MOVEMEM 0565 BYE 0570 RTS 0575 ; 0580 .END MEMMOVE copies any number of bytes in ascending order from the source to destination. Earlier the article mentioned an Atari BASIC string hack that assigns strings to specific memory addresses and uses the string assignment action to copy from a source address to a target address. String assignments work in ascending order. Therefore, this routine achieves the same result as the string method (ascending copy) with a bit less hacking of the BASIC variable table. Copying in reverse order or automatically detecting source and target overlap would add more code for options that are not as frequently needed. This is certainly not the highest performing option possible. But, it is fairly compact and uses Page zero allowing the routine to be general-purpose, and reusable, and that is a reasonable goal for a routine to support Atari BASIC. If this discussion concerned writing an entire video game in assembly then the code would focus on execution time and use unrolled loops or other bells and whistles for copying faster at the expense of code size. Testing MEMMOVE The Atari BASIC program below, TESTMOVE.BAS, reasonably verifies operation of the memory move utility works as expected: 100 REM TEST MEMORY MOVE OPERATIONS 105 REM 110 GRAPHICS 0:POKE 710,0:POKE 82,0 115 DIM S$(258),D$(259) 120 GOSUB 503:REM RESET SRC AND DST 125 GOSUB 10000:REM LOAD MEMMOVE 130 REM RUN TESTS FOR 8,255,256,257 135 RESTORE 230 140 READ MSIZE 145 IF MSIZE<1 THEN ? "Done":END 150 ? "Testing move size ";MSIZE;" . . . "; 155 X=USR(MMOV,ADR(S$),ADR(D$)+1,MSIZE) 160 DCOUNT=0 165 FOR I=1 TO 259 170 IF D$(I,I)="*" THEN DCOUNT=DCOUNT+1 175 NEXT I 180 IF DCOUNT=MSIZE THEN ? "OK":GOTO 190 185 ? "FAILED! ";DCOUNT;" <> ";MSIZE 190 GOSUB 504:REM RESET DST 195 GOTO 140 200 REM 205 REM NUMBER OF BYTES TO MOVE FOR 210 REM EACH TEST. TESTS LESS THAN 215 REM ONE PAGE AND THE BORDER 220 REM CONDITIONS AROUND ONE PAGE. 225 REM 230 DATA 8,255,256,257,-1 235 END 500 REM 501 REM RESET MEMORY 502 REM 503 S$="*":S$(258)="*":S$(2)=S$ 504 D$=".":D$(259)=".":D$(2)=D$ 505 RETURN 9997 REM 9998 REM SETUP ML MEMMOVE UTILITY 9999 REM 10000 DIM MM$(68) 10001 MMOV=ADR(MM$) 10002 RESTORE 24000:? "Loading MMOV..." 10003 FOR I=0 TO 67 10004 READ D:POKE MMOV+I,D 10005 NEXT I:? 10006 RETURN 23996 REM H1:MEMMOVE.OBJ 23997 REM SIZE = 68 23998 REM START = 37376 23999 REM END = 37443 24000 DATA 160,0,132,212,132,213,104,240 24001 DATA 58,10,168,201,6,240,5,104 24002 DATA 136,208,252,96,104,153,213,0 24003 DATA 136,208,249,165,215,133,213,165 24004 DATA 214,133,212,160,0,166,215,240 24005 DATA 14,177,218,145,216,200,208,249 24006 DATA 230,219,230,217,202,208,242,166 24007 DATA 214,240,8,177,218,145,216,200 24008 DATA 202,208,248,96 The program tests different sizes to verify the partial and full page copy loops, and also tests the border conditions around a full page copy. After each each copy it tests the contents of the destination memory counting all the locations that were changed by the memory move. Successful output will look like this: Below are the source files and examples of how to load the machine language routine into BASIC included in the disk image and archive: MEMMOVE File List: MEMMOVE.M65 Saved Mac/65 source MEMMOVE.L65 Mac/65 source listing MEMMOVE.T65 Mac/65 source listed to H6: (linux) MEMMOVE.ASM Mac/65 assembly listing MEMMOVE.TSM Mac/65 assembly listing to H6: (linux) MEMMOVE.OBJ Mac/65 assembled machine language program (with load segments) MEMMOVE.BIN Assembled machine language program without load segments MEMMOVE.LIS LISTed DATA statements for MEMMOVE.BIN routine. MEMMOVE.TLS LISTed DATA statements for MEMMOVE.BIN routine to H6: (linux) MAKEMMOV.BAS BASIC program to create the MEMMOVE.BIN file. This also contains the MEMMOVE routine in DATA statements. MAKEMMOV.LIS LISTed version of MAKEMMOV.BAS MAKEMMOV.TLS LISTed version of MAKEMMOV.BAS to H6: (linux) TESTMOVE.BAS BASIC program that tests the MEMMOVE USR() routine. TESTMOVE.LIS LISTed version of TESTMOVE.BAS. TESTMOVE.TLS LISTed version of TESTMOVE.BAS to H6: (linux) ZIP archive of files: Move_Disk.zip Tar archive of files (remove the .zip after download) Move_Disk.tgz.zip Great peace have those who love your law; nothing can make them stumble. Psalm 119:165
  9. Convert Integer To Bit String ============================================================== Part 1 - Introduction http://atariage.com/forums/blog/576/entry-13175-part-1-of-11-simple-assembly-for-atari-basic/ Part 2 - Learn 82.7% of Assembly Language in About Three Pages http://atariage.com/forums/blog/576/entry-13176-part-2-of-11-simple-assembly-for-atari-basic/ Part 3 - The World Inside a USR() Routine http://atariage.com/forums/blog/576/entry-13177-part-3-of-11-simple-assembly-for-atari-basic/ Part 4 - Implement DPEEK() http://atariage.com/forums/blog/576/entry-13178-part-4-of-11-simple-assembly-for-atari-basic/ Part 5 - Implement DPOKE http://atariage.com/forums/blog/576/entry-13180-part-5-of-11-simple-assembly-for-atari-basic/ Part 6 - Various Bit Manipulations http://atariage.com/forums/blog/576/entry-13181-part-6-of-11-simple-assembly-for-atari-basic/ Part 7 - Convert Integer to Hex String http://atariage.com/forums/blog/576/entry-13182-part-7-of-11-simple-assembly-for-atari-basic/ Part 8 - Convert Integer to Bit String http://atariage.com/forums/blog/576/entry-13183-part-8-of-11-simple-assembly-for-atari-basic/ Part 9 - Memory Copy http://atariage.com/forums/blog/576/entry-13184-part-9-of-11-simple-assembly-for-atari-basic/ Part 10 - Binary File I/O Part 1 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13185-part-10-of-11-simple-assembly-for-atari-basic/ Part 11 - Binary File I/O Part 2 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13186-part-11-simple-assembly-for-atari-basic-the-end/ ============================================================== This machine language routine converts a 16-bit integer into a string of values representing 0 and 1 bits. This is useful for visualizing bits in a byte, word, or address for diagnostic purposes. This is practical on the Atari for displaying character set or player/missile graphics data in a human-readable way. This routine has a different number of arguments from the INT2HEX string conversion routine and so is presented on its own. This routine has similar problems to solve as the INT2HEX routine. It must deal with the low byte stored in memory before the high byte, because it must output the bit values in order of highest bits first and lowest bits last. (e.g 258 or hex $0102 should be output as “0000000100000010” though it is stored as $02 $01). Converting a byte to a string of bits is fairly easy in 6502. In general terms: test the high bit and output a “0” or a “1” as needed, then shift the bits of the byte and repeat until 8 bits have been extracted. The routine below includes a touch of customization – the USR() routine may also specify the characters (ASCII/ATASCII) used to represent “0” and “1”. INT2BITS in Mac/65 Assembler Code 0100 ; INT2BITS 0105 ; 0110 ; Convert 16-bit integer into 0115 ; string of bits. 0120 ; 0125 ; INT2BITS USR 2 arguments: 0130 ; Value == Integer to convert. 0135 ; StrAdr == Address of string 0140 ; which must be able 0145 ; to hold 16 characters 0150 ; Zero == Character used for 0155 ; clear bit. 0160 ; One == Character used for 0165 ; set bit. 0170 ; 0175 ; USR return value 0 means no 0180 ; conversion. Non-Zero means 0185 ; STRADR contains data. 0190 ; 0195 ; Use the FR0/FR1 FP register. 0200 ; The return value for BASIC 0205 ; goes in FR0. 0210 ; No FP is used so all of FR0 0215 ; (and more FP registers) can 0220 ; be considered available. 0225 ; 0230 ZRET = $D4 ; FR0 $D4/$D5 Return value 0235 ZARGS = $D5 ; $D6-1 for arg Pulldown loop 0240 ZONE = $D6 ; FR0 $D6/$D7 char for "1" 0245 ZZERO = $D8 ; FR0 $D8/$D9 char for "0" 0250 ZSTR = $DA ; FR0 $DA/$DB StrAdr 0255 ZVAL = $DC ; FR0 $DC/$DD Integer value 0260 ; 0265 .OPT OBJ 0270 ; 0275 ; Arbitrary. This is relocatable. 0280 ; 0285 *= $9300 0290 ; 0295 INIT 0300 LDA #$00 ; Make sure return 0305 STA ZRET ; value is cleared 0310 STA ZRET+1 ; by default. 0315 PLA ; Get argument count 0320 BEQ EXIT ; Shortcut for no args 0325 ASL A ; Now number of bytes 0330 TAY 0335 CMP #$08 ; Integer Value1, StrAdr, Zero, One 0340 BEQ PULLDOW N0345 ; 0350 ; Bad args. Clean up for exit. 0355 ; 0360 DISPOSE ; any number of args 0365 PLA 0370 DEY 0375 BNE DISPOSE 0380 RTS ; Abandon ship 0385 ; 0390 ; This code works the same 0395 ; for 1, 4, 8 ... arguments. 0400 ; 0405 PULLDOWN 0410 PLA 0415 STA ZARGS,Y 0420 DEY 0425 BNE PULLDOWN 0430 ; 0435 ; Arg validation. 0440 ; StrAdr may not be null. 0445 ; Zero and One may not 0450 ; be equal. 0455 ; 0460 LDA ZSTR 0465 ORA ZSTR+1 0470 BEQ EXIT ; zstr is null 0475 LDA ZZERO 0480 CMP ZONE 0485 BEQ EXIT ; zero == one 0490 ; 0495 ; Break Integer Value into 0500 ; bytes and test bits from 0505 ; high to low... 0510 ; 0515 LDX #$01 ; X index bytes. 0520 ; Y is already 0 0525 ; Y index string. 0530 ; 0535 WALK_BITS 0540 CLC 0545 ASL ZVAL,X ; Shift out bit 0550 LDA ZZERO ; Assume Zero 0555 BCC COPY2STR ; Copy if so 0560 LDA ZONE ; Actually One 0565 COPY2STR 0570 STA (ZSTR),Y 0575 INY ; Next string position 0580 CPY #$08 ; End of high byte? 0585 BEQ NEXT_BYTE ; Yes. Dec index. 0590 CPY #$10 ; End of low byte? 0595 BNE WALK_BITS ; No, loop again. 0600 NEXT_BYTE 0605 DEX ; 1, 0 will continue 0610 BPL WALK_BITS ; -1 ends loop 0615 END_LOOP 0620 ; 0625 INC ZRET ; Successful return 0630 EXIT 0635 RTS 0640 ; 0645 .END The variable validation has a trivial addition to make sure the character values for “0” and “1” are not the same: 0475 LDA ZZERO 0480 CMP ZONE 0485 BEQ EXIT ; zero == one Unlike the hex conversion there is just one part to the bit conversion: 0515 LDX #$01 ; X index bytes. 0520 ; Y is already 0 0525 ; Y index string. 0530 ; 0535 WALK_BITS 0540 CLC 0545 ASL ZVAL,X ; Shift out bit 0550 LDA ZZERO ; Assume Zero 0555 BCC COPY2STR ; Copy if so 0560 LDA ZONE ; Actually One 0565 COPY2STR 0570 STA (ZSTR),Y 0575 INY ; Next string position 0580 CPY #$08 ; End of high byte? 0585 BEQ NEXT_BYTE ; Yes. Dec index. 0590 CPY #$10 ; End of low byte? 0595 BNE WALK_BITS ; No, loop again. 0600 NEXT_BYTE 0605 DEX ; 1, 0 will continue 0610 BPL WALK_BITS ; -1 ends loop 0615 END_LOOP There are two loops happening here. One loop, indexed by X, counts in reverse, 1 to 0, from the high byte to the low byte of the integer. Within that loop is the activity to output all the bits in the byte which is done as part of the loop indexed by Y to count from character position 0 to 15. When the Y loop reaches values 8 and 16 it signals that all the bits in the byte have been output, so then it is time to continue the outer loop to get the next byte. The Y index is thus incremented 8 times for each decrement (one byte) of the X loop. As with the integer to hexadecimal conversion this utility receives the address of the output string, not its control information, so it can only fill in the bytes in the string and can't change the size as BASIC understands it. A BASIC program using the results of the conversion must insure that it populates the length of the string prior to calling the conversion utility: 10 DIM A$(16) 20 A$=" ":REM 16 SPACES Now that we have routines to convert values into hex strings and bit representation, the test program for BITS can be revisited to provide improved diagnostic information. Testing BITS Again (with INT2HEX and INT2BITS) Below is the Atari BASIC TESTBITS.BAS program upgraded with the new conversion utilities to produce much more sensible output illustrating the changes in the bits: 100 REM TESTBIT2.BAS 105 REM TEST BIT OPERATIONS UTILITY 2 110 GRAPHICS 0:POKE 710,0:POKE 82,0 115 DIM B(3,1),N$(21),HX$(5),BIT$(17) 120 N$="OR ANDEORLSRLSLRORROL" 125 HX$=" ":BIT$=" " 130 AZERO=48:AONE=49:REM ATASCII 0 1 135 REM ENUMERATE BIT OPERATIONS 140 RESTORE 150 145 READ BOR,BAND,BEOR,BLSR,BLSL,BROR,BROL 150 DATA 1,2,3,4,5,6,7 155 GOSUB 10000:REM BITS UTILITY 160 RESTORE 190:REM BIT PATTERNS 165 FOR X=0 TO 1 170 FOR Y=0 TO 3 175 READ D:B(Y,X)=D 180 NEXT Y 185 NEXT X 190 DATA 0,257,0,257,0,0,258,258 195 REM 200 REM TEST OR, AND, EOR 205 REM 210 FOR OPER=BOR TO BEOR 215 FOR Y=0 TO 3 220 ? " "; 225 VALUE=B(Y,0):GOSUB 405 230 ? N$(OPER*3-2,OPER*3); 235 VALUE=B(Y,1):GOSUB 405 240 ? "=== ===== ===================" 245 VALUE=USR(BITS,OPER,B(Y,0),B(Y,1)) 250 ? " ";:GOSUB 405 255 ? 260 NEXT Y 265 NEXT OPER 270 REM 275 REM TEST SHIFT AND ROTATE 280 REM 285 V=129:REM $81 290 FOR OPER=BLSR TO BROL 295 ? "Testing ";N$(OPER*3-2,OPER*3);" on Value "; 300 VALUE=V:GOSUB 505 305 FOR Y=0 TO 8 310 ? N$(OPER*3-2,OPER*3);" ";Y;" = "; 315 VALUE=USR(BITS,OPER,V,Y) 320 GOSUB 505 325 NEXT Y 330 ? 335 NEXT OPER 340 END 400 REM 401 REM OUTPUT INTEGER CONVERTED TO 402 REM USEFUL FORMAT: 403 REM $HEX = BITS BITS BITS BITS 404 REM 405 RH=USR(INT2HEX,VALUE,ADR(HX$)) 406 RB=USR(BIT2STR,VALUE,ADR(BIT$),AZERO,AONE) 407 ? " $";HX$;" = ";BIT$(1,4);" ";BIT$(5,;" ";BIT$(9,12);" ";BIT$(13,16) 408 RETURN 500 REM 501 REM OUTPUT BYTE VALUE CONVERTED TO 502 REM USEFUL FORMAT: 503 REM $HEX = BITS BITS 504 REM 505 RH=USR(INT2HEX,VALUE,ADR(HX$)) 506 RB=USR(BIT2STR,VALUE,ADR(BIT$),AZERO,AONE) 507 ? "$";HX$(3,4);" = ";BIT$(9,12);" ";BIT$(13,16) 508 RETURN 9997 REM 9998 REM SETUP ML UTILITIES 9999 REM 10000 DIM BT$(162),B2S$(67),I2H$(72) 10001 BITS=ADR(BT$) 10002 RESTORE 27000:? "Loading BITS" 10003 FOR I=0 TO 161 10004 READ D:POKE BITS+I,D 10005 NEXT I:? 10006 BIT2STR=ADR(B2S$) 10007 RESTORE 25000:? "Loading BIT2STR" 10008 FOR I=0 TO 66 10009 READ D:POKE BIT2STR+I,D 10010 NEXT I:? 10011 INT2HEX=ADR(I2H$) 10012 RESTORE 26000:? "Loading INT2HEX" 10013 FOR I=0 TO 71 10014 READ D:POKE INT2HEX+I,D 10015 NEXT I:? 10016 RETURN 24996 REM H1:INT2BITS.OBJ 24997 REM SIZE = 67 24998 REM START = 37632 24999 REM END = 37698 25000 DATA 169,0,133,212,133,213,104,240 25001 DATA 57,10,168,201,8,240,5,104 25002 DATA 136,208,252,96,104,153,213,0 25003 DATA 136,208,249,165,218,5,219,240 25004 DATA 33,165,216,197,214,240,27,162 25005 DATA 1,24,22,220,165,216,144,2 25006 DATA 165,214,145,218,200,192,8,240 25007 DATA 4,192,16,208,236,202,16,233 25008 DATA 230,212,96 25996 REM H1:INT2HEX.OBJ 25997 REM SIZE = 72 25998 REM START = 37888 25999 REM END = 37959 26000 DATA 169,0,133,212,133,213,104,240 26001 DATA 62,10,168,201,4,240,5,104 26002 DATA 136,208,252,96,104,153,213,0 26003 DATA 136,208,249,5,215,240,40,216 26004 DATA 162,1,181,216,74,74,74,74 26005 DATA 145,214,181,216,41,15,200,145 26006 DATA 214,200,202,16,237,136,177,214 26007 DATA 201,10,144,2,105,6,105,48 26008 DATA 145,214,136,16,241,230,212,96 26996 REM H1:BITS.OBJ 26997 REM SIZE = 162 26998 REM START = 38144 26999 REM END = 38305 27000 DATA 169,0,133,212,133,213,104,240 27001 DATA 43,10,168,201,6,240,5,104 27002 DATA 136,208,252,96,104,153,213,0 27003 DATA 136,208,249,164,218,240,21,136 27004 DATA 240,19,136,240,29,136,240,39 27005 DATA 136,240,49,136,240,61,136, 27006 DATA 73,136,240,90,96,165,216,5 27007 DATA 214,133,212,165,217,5,215,133 27008 DATA 213,96,165,216,37,214,133,212 27009 DATA 165,217,37,215,133,213,96,165 27010 DATA 216,69,214,133,212,165,217,69 27011 DATA 215,133,213,96,165,216,133,212 27012 DATA 166,214,240,208,74,202,208,252 27013 DATA 133,212,96,165,216,133,212,166 27014 DATA 214,240,193,10,202,208,252,133 27015 DATA 212,96,165,216,133,212,166,214 27016 DATA 240,178,24,106,144,2,9,128 27017 DATA 202,208,247,133,212,96,165,216 27018 DATA 133,212,166,214,240,158,24,42 27019 DATA 144,2,9,1,202,208,247,133 27020 DATA 212,96 Here is the kinder, gentler TESTBIT2 output with the values and bits illustrated: Loading BITS Loading BIT2STR Loading INT2HEX $0000 = 0000 0000 0000 0000 OR $0000 = 0000 0000 0000 0000 === ===== =================== $0000 = 0000 0000 0000 0000 $0101 = 0000 0001 0000 0001 OR $0000 = 0000 0000 0000 0000 === ===== =================== $0101 = 0000 0001 0000 0001 $0000 = 0000 0000 0000 0000 OR $0102 = 0000 0001 0000 0010 === ===== =================== $0102 = 0000 0001 0000 0010 $0101 = 0000 0001 0000 0001 OR $0102 = 0000 0001 0000 0010 === ===== =================== $0103 = 0000 0001 0000 0011 $0000 = 0000 0000 0000 0000 AND $0000 = 0000 0000 0000 0000 === ===== =================== $0000 = 0000 0000 0000 0000 $0101 = 0000 0001 0000 0001 AND $0000 = 0000 0000 0000 0000 === ===== =================== $0000 = 0000 0000 0000 0000 $0000 = 0000 0000 0000 0000 AND $0102 = 0000 0001 0000 0010 === ===== =================== $0000 = 0000 0000 0000 0000 $0101 = 0000 0001 0000 0001 AND $0102 = 0000 0001 0000 0010 === ===== =================== $0100 = 0000 0001 0000 0000 $0000 = 0000 0000 0000 0000 EOR $0000 = 0000 0000 0000 0000 === ===== =================== $0000 = 0000 0000 0000 0000 $0101 = 0000 0001 0000 0001 EOR $0000 = 0000 0000 0000 0000 === ===== =================== $0101 = 0000 0001 0000 0001 $0000 = 0000 0000 0000 0000 EOR $0102 = 0000 0001 0000 0010 === ===== =================== $0102 = 0000 0001 0000 0010 $0101 = 0000 0001 0000 0001 EOR $0102 = 0000 0001 0000 0010 === ===== =================== $0003 = 0000 0000 0000 0011 Testing LSR on Value $81 = 1000 0001 LSR 0 = $81 = 1000 0001 LSR 1 = $40 = 0100 0000 LSR 2 = $20 = 0010 0000 LSR 3 = $10 = 0001 0000 LSR 4 = $08 = 0000 1000 LSR 5 = $04 = 0000 0100 LSR 6 = $02 = 0000 0010 LSR 7 = $01 = 0000 0001 LSR 8 = $00 = 0000 0000 Testing LSL on Value $81 = 1000 0001 LSL 0 = $81 = 1000 0001 LSL 1 = $02 = 0000 0010 LSL 2 = $04 = 0000 0100 LSL 3 = $08 = 0000 1000 LSL 4 = $10 = 0001 0000 LSL 5 = $20 = 0010 0000 LSL 6 = $40 = 0100 0000 LSL 7 = $80 = 1000 0000 LSL 8 = $00 = 0000 0000 Testing ROR on Value $81 = 1000 0001 ROR 0 = $81 = 1000 0001 ROR 1 = $C0 = 1100 0000 ROR 2 = $60 = 0110 0000 ROR 3 = $30 = 0011 0000 ROR 4 = $18 = 0001 1000 ROR 5 = $0C = 0000 1100 ROR 6 = $06 = 0000 0110 ROR 7 = $03 = 0000 0011 ROR 8 = $81 = 1000 0001 Testing ROL on Value $81 = 1000 0001 ROL 0 = $81 = 1000 0001 ROL 1 = $03 = 0000 0011 ROL 2 = $06 = 0000 0110 ROL 3 = $0C = 0000 1100 ROL 4 = $18 = 0001 1000 ROL 5 = $30 = 0011 0000 ROL 6 = $60 = 0110 0000 ROL 7 = $C0 = 1100 0000 ROL 8 = $81 = 1000 0001 The output is considerably easier to understand. So neat, so organized, so obvious and informative! Below are the source files and examples of how to load the machine language routine into BASIC included in the disk image and archive: INT2BITS File List: INT2BITS.M65 Saved Mac/65 source INT2BITS.L65 Mac/65 source listing INT2BITS.T65 Mac/65 source listed to H6: (linux) INT2BITS.ASM Mac/65 assembly listing INT2BITS.TSM Mac/65 assembly listing to H6: (linux) INT2BITS.OBJ Mac/65 assembled machine language program (with load segments) INT2BITS.BIN Assembled machine language program without load segments INT2BITS.LIS LISTed DATA statements for INT2BITS.BIN routine. INT2BITS.TLS LISTed DATA statements for INT2BITS.BIN routine to H6: (linux) MAKEI2B.BAS BASIC program to create the INT2BITS.BIN file. This also contains the INT2BITS routine in DATA statements. MAKEI2B.LIS LISTed version of MAKEI2B.BAS MAKEI2B.TLS LISTed version of MAKEI2B.BAS to H6: (linux) TESTI2B.BAS BASIC program that tests the INT2BITS USR() routines. TESTI2B.LIS LISTed version of TESTI2B.BAS. TESTI2B.TLS LISTed version of TESTI2B.BAS to H6: (linux) ZIP archive of files: Convert_Disk.zip Tar archive of files (remove the .zip after download) Convert_Disk.tgz.zip The Lord will fulfill his purpose for me; your steadfast love, O Lord, endures forever. Do not forsake the work of your hands. Psalm 138:8
  10. Convert Integer To Hex String ============================================================== Part 1 - Introduction http://atariage.com/forums/blog/576/entry-13175-part-1-of-11-simple-assembly-for-atari-basic/ Part 2 - Learn 82.7% of Assembly Language in About Three Pages http://atariage.com/forums/blog/576/entry-13176-part-2-of-11-simple-assembly-for-atari-basic/ Part 3 - The World Inside a USR() Routine http://atariage.com/forums/blog/576/entry-13177-part-3-of-11-simple-assembly-for-atari-basic/ Part 4 - Implement DPEEK() http://atariage.com/forums/blog/576/entry-13178-part-4-of-11-simple-assembly-for-atari-basic/ Part 5 - Implement DPOKE http://atariage.com/forums/blog/576/entry-13180-part-5-of-11-simple-assembly-for-atari-basic/ Part 6 - Various Bit Manipulations http://atariage.com/forums/blog/576/entry-13181-part-6-of-11-simple-assembly-for-atari-basic/ Part 7 - Convert Integer to Hex String http://atariage.com/forums/blog/576/entry-13182-part-7-of-11-simple-assembly-for-atari-basic/ Part 8 - Convert Integer to Bit String http://atariage.com/forums/blog/576/entry-13183-part-8-of-11-simple-assembly-for-atari-basic/ Part 9 - Memory Copy http://atariage.com/forums/blog/576/entry-13184-part-9-of-11-simple-assembly-for-atari-basic/ Part 10 - Binary File I/O Part 1 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13185-part-10-of-11-simple-assembly-for-atari-basic/ Part 11 - Binary File I/O Part 2 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13186-part-11-simple-assembly-for-atari-basic-the-end/ ============================================================== OSS BASIC XL includes the Hex$() function that converts an integer value to a string of hexadecimal digits. That would be a useful accessory to the bit manipulation test program, since byte values are easier to visualize in hexadecimal. The integer to hexadecimal string conversion sounds more simple than it is. An integer made of low byte value, $12, and high byte value, $34, exists in memory in the order: $12, $34. However, the hexadecimal text representation is in reverse order with high byte first, “3412”. Furthermore, the text representation is four characters (bytes) as “3”, “4”, “1”, “2”. This means the two bytes of the integer must be separated into individual nybble values and reorganized in the string order. Next, the actual binary values of each nybble (0 to 15) must be converted into a text character. The ASCII/ATASCII characters “0” through “9” are not contiguous with the characters “A” through “F”. A look-up table of 16 text characters would be the most simple and direct method, but that would not be relocatable. So, a comparison/computation method is used, and discussed below: INT2HEX in Mac/65 Assembler Code 0100 ; INT2HEX.M65 0105 ; 0110 ; Convert 16-bit integer into 0115 ; hex string. 0120 ; 0125 ; INT2HEX USR 2 arguments: 0130 ; Value == Integer to convert. 0135 ; StrAdr == Address of string 0140 ; which must be able 0145 ; to hold 4 characters 0150 ; 0155 ; USR return value 0 means no 0160 ; conversion. Non-Zero means 0165 ; STRADR contains hex string. 0170 ; 0175 ; Use the FR0/FR1 FP register. 0180 ; The return value for BASIC 0185 ; goes in FR0. 0190 ; No FP is used so all of FR0 0195 ; (and more FP registers) can 0200 ; be considered available. 0205 ; 0210 ZRET = $D4 ; FR0 $D4/$D5 Return value 0215 ZARGS = $D5 ; $D6-1 for arg Pulldown loop 0220 ZSTR = $D6 ; FR0 $D6/$D7 STRADR 0225 ZVAL = $D8 ; FR0 $D8/$D9 Integer value 0230 ; 0235 .OPT OBJ 0240 ; 0245 ; Arbitrary. This is relocatable. 0250 ; 0255 *= $9400 0260 ; 0265 INIT 0270 LDA #$00 ; Make sure return 0275 STA ZRET ; value is cleared 0280 STA ZRET+1 ; by default. 0285 PLA ; Get argument count 0290 BEQ EXIT ; Shortcut for no args 0295 ASL A ; Now number of bytes 0300 TAY 0305 CMP #$04 ; Integer Value1, StrAdr 0310 BEQ PULLDOWN 0315 ; 0320 ; Bad args. Clean up for exit. 0325 ; 0330 DISPOSE ; any number of args 0335 PLA 0340 DEY 0345 BNE DISPOSE 0350 RTS ; Abandon ship 0355 ; 0360 ; This code works the same 0365 ; for 1, 4, 8 ... arguments. 0370 ; 0375 PULLDOWN 0380 PLA 0385 STA ZARGS,Y 0390 DEY 0395 BNE PULLDOWN 0400 ; 0405 ; Arg validation. 0410 ; StrAdr may not be null. 0415 ; 0420 LDA ZSTR 0425 ORA ZSTR+1 0430 BEQ EXIT ; zstr is null 0435 ; 0440 CLD 0445 ; 0450 ; Split Integer Value into 0455 ; nybbles and temporarily store 0460 ; in the String output... 0465 ; 0470 LDX #$01 ; X index bytes. 0475 ; Y is already 0 0480 ; Y index string. 0485 ; 0490 SPLIT_BYTES 0495 LDA ZVAL,X ; Byte 0500 ; 0505 ; Right shift 4 to keep 0510 ; the high nybble. 0515 LSR A 0520 LSR A 0525 LSR A 0530 LSR A 0535 STA (ZSTR),Y ; Save for later 0540 ; 0545 ; Now, do the low nybble 0550 LDA ZVAL,X ; Byte again 0555 AND #$0F ; low nybble 0560 INY ; Next char in string 0565 STA (ZSTR),Y ; Save for later 0570 ; 0575 INY ; Next char in string 0580 DEX ; Next integer byte 0585 BPL SPLIT_BYTES 0590 ; 0595 ; Next, convert the nybbles 0600 ; saved in the string into the 0605 ; final ASCII form. 0610 ; 0615 DEY ; Correct string index. 0620 BYTE2HEX 0625 LDA (ZSTR),Y 0630 CMP #$0A ; 10 or greater? 0635 BCC ADD_48 ; No, 0 to 9. 0640 ; 0645 ADC #$06 ; "A"-"0"-$0A-Carry 0650 ADD_48 0655 ADC #$30 0660 STA (ZSTR),Y 0665 DEY ; 3, 2, 1, 0 will continue 0670 BPL BYTE2HEX ; -1 ends loop 0675 ; 0680 INC ZRET ; Successful return 0685 EXIT 0690 RTS 0695 ; 0700 .END There is a new bit of code in the initialization: 0405 ; Arg validation. 0410 ; StrAdr may not be null. 0415 ; 0420 LDA ZSTR 0425 ORA ZSTR+1 0430 BEQ EXIT ; zstr is null 0435 ; 0440 CLD This shows a short way to compare an address (or integer) to NULL/zero. It simply OR's the low and high bytes together. Then if any bit is set it means the address is not NULL. Any non-zero value is considered legitimate (which is not a purely correct assumption, but reasonably good enough for this exercise.) The long way to check the string address would be to load low byte, compare to zero. If it is not zero, then the value is valid and so branch to the code to continue the routine. If it is zero, then load the high byte, compare to zero, and take the successful branch for non-zero and exit the routine if it is zero. That would take six instructions, handily duplicated by the three here. Full validation (not done here) would mean a lot more code making sure the value is a valid address for a BASIC string, or otherwise not anywhere near low memory and not in the ROM area. Finally, this code turns off BCD mode (CLD) because it will use Add instructions on binary values later. The conversion is separated into two activities: First is separating the bytes into nybbles and placing them in the correct order. The second phase is converting the nybbles into the corresponding ASCII/ATASCII characters. The code could be done in just one loop, but that would mean duplicating the nybble to text conversion code twice in the loop. (And that idea could be implemented more efficiently as a subroutine called by JSR, but then the code would not be relocatable.) The first part of the conversion: 0490 SPLIT_BYTES 0495 LDA ZVAL,X ; Byte 0500 ; 0505 ; Right shift 4 to keep 0510 ; the high nybble. 0515 LSR A 0520 LSR A 0525 LSR A 0530 LSR A 0535 STA (ZSTR),Y ; Save for later 0540 ; 0545 ; Now, do the low nybble 0550 LDA ZVAL,X ; Byte again 0555 AND #$0F ; low nybble 0560 INY ; Next char in string 0565 STA (ZSTR),Y ; Save for later 0570 ; 0575 INY ; Next char in string 0580 DEX ; Next integer byte 0585 BPL SPLIT_BYTES There are two loops happening here. One loop, indexed by X, counts in reverse, 1 to 0, from the high byte to the low byte of the integer. Within that loop is the activity to separate the two nybbles in the byte. Mixed in this loop is another loop, indexed by Y, counting from 0 to 3 to index each character position of the string. After the routine liberates the nybbles from their byte it stores the nybbles in their respective positions in the string. The Y index is thus incremented twice (two characters) for each decrement (one byte) of the X loop. The end result is a “string” containing the four, binary nybble values each in a separate “character”. The binary values are not human readable, so the routine must convert the binary values to corresponding ASCII/ATASCII characters: 0620 BYTE2HEX 0625 LDA (ZSTR),Y 0630 CMP #$0A ; 10 or greater? 0635 BCC ADD_48 ; No, 0 to 9. 0640 ; 0645 ADC #$06 ; "A"-"0"-$0A-Carry 0650 ADD_48 0655 ADC #$30 0660 STA (ZSTR),Y 0665 DEY ; 3, 2, 1, 0 will continue 0670 BPL BYTE2HEX ; -1 ends loop This second part of the conversion code loops though the string in reverse converting each byte value into the corresponding ASCII/ATASCII value. The obvious method to do this would be a lookup table that translates the 16 possible values into the corresponding character. However, a lookup table is not easily relocatable, so this version does the conversion by value comparisons and math. Here is the problem laid out in one table. The math must convert one series of values (“Values”) into another series of values (“Text Values”) that will print as text characters. Values ASCII Text Text Values Difference $00 “0” $30 $30 $01 “1” $31 $30 $02 “2” $32 $30 $03 “3” $33 $30 $04 “4” $34 $30 $05 “5” $35 $30 $06 “6” $36 $30 $07 “7” $37 $30 $08 “8” $38 $30 $09 “9” $39 $30 $0A “A” $41 $37 $0B “B” $42 $37 $0C “C” $43 $37 $0D “D” $44 $37 $0E “E” $45 $37 $0F “F” $46 $37 This tells us a few facts. The original series (“Values”) breaks down to two destination series with different offsets, $30 and $37, and that the value of the offset increases rather than decreases ($30 < $37) with each series. Since the $A to $F text values are after the $0 to $9 values, then only addition is needed to convert the binary “Values” series into the “Text Values” series. So, the code merely needs to determine whether the value is in the first series or the second series and then add the difference accordingly. However, to squeeze code size a small bit of cleverness is added that capitalizes on the relationship between the offsets for each series. At each position the code tests to determine if the value is in the $0 to $9 range or the $A to $F range. Values $0 to $9 result in a branch to code that adds $30 to the value, converting binary values $0 to $9 to ASCII/ATASCII values “0” to “9” (or $30 to $39). That branch bypasses other code below for handling values $A to $F. The conversion for values $A to $F is in two parts – first, the code adds an offset. On first glance at the table above this offset should be the difference of $37 - $30 or $7. The first two conditions below do result in the $07 offset. But, an easily overlooked condition in the code is the carry bit acquired by the comparison. So, the offset value is decremented to $6 to compensate for the carry bit: 1. the difference between the “A” and “0” characters ($41 - $30 = $11) and then 2. it subtracts the integer value of the start of the $A to $F range ($11 - $A == $7) and then 3. it removes the Carry bit that is acquired by the comparison operation ($7 - $1 == $6). After adding the offset the execution path falls into the same section used for $0 to $9 which adds $30 to the value. So, for binary value $A the end result is $A + $6 + Carry + $30 which is $41 or ASCII/ATASCII “A”. The entire working code is 15 bytes long. The actual code using a lookup table to convert binary to ASCII would be much shorter (and faster), but still require an additional 16 bytes of supporting data for the translation table. Since BASIC can pass the address of the string data, not not the control information of the string, the machine language routine has no way of knowing the size of the string – either the DIM'ensioned size or the actual string length. Therefore the machine language code can neither limit itself to the maximum string length nor change the string's current length while populating the text value. A BASIC program using the results of the conversion must insure that it sets the real length of the string prior to calling the conversion function. This is simple – the BASIC program need only assign a value to the string that is four characters long prior to using the machine language routine. Such as: 130 DIM A$(4) 140 A$=" ":REM FOUR SPACES The conversion routine overwrites the contents of the string replacing the blank spaces with the hexadecimal text string. The BASIC program can then safely refer to the string contents, like this: 150 RH=USR(INT2HEX,4660,ADR(A$)) 160 ? 4660;"(dec) = $";A$ 170 ? "Low Byte = $";A$(3,4) 180 ? "High Byte = $";A$(1,2) which results in this output: 4660(dec) = $1234 Low Byte = $34 High Byte = $12 Below are the source files and examples of how to load the machine language routine into BASIC included in the disk image and archive: INT2HEX File List: INT2HEX.M65 Saved Mac/65 source INT2HEX.L65 Mac/65 source listing INT2HEX.T65 Mac/65 source listed to H6: (linux) INT2HEX.ASM Mac/65 assembly listing INT2HEX.TSM Mac/65 assembly listing to H6: (linux) INT2HEX.OBJ Mac/65 assembled machine language program (with load segments) INT2HEX.BIN Assembled machine language program without load segments INT2HEX.LIS LISTed DATA statements for INT2HEX.BIN routine. INT2HEX.TLS LISTed DATA statements for INT2HEX.BIN routine to H6: (linux) MAKEI2H.BAS BASIC program to create the INT2HEX.BIN file. This also contains the INT2HEX routine in DATA statements. MAKEI2H.LIS LISTed version of MAKEI2H.BAS MAKEI2H.TLS LISTed version of MAKEI2H.BAS to H6: (linux) TESTI2H.BAS BASIC program that tests the INT2HEX USR() routines. TESTI2H.LIS LISTed version of TESTI2H.BAS. TESTI2H.TLS LISTed version of TESTI2H.BAS to H6: (linux) But, let's say that you're really so lazy that its still too much effort for you to visualize the bits in each hex value. The solution to that problem is the next routine that converts a 16-bit integer into a string of 16 characters where each character represents a bit value, 0 or 1. ZIP archive of files: Convert_Disk.zip Tar archive of files (remove the .zip after download) Convert_Disk.tgz.zip Do not be anxious about anything, but in everything by prayer and supplication with thanksgiving let your requests be made known to God. And the peace of God, which surpasses all understanding, will guard your hearts and your minds in Christ Jesus. Philippians 4:6-7
  11. DPOKE ============================================================== Part 1 - Introduction http://atariage.com/forums/blog/576/entry-13175-part-1-of-11-simple-assembly-for-atari-basic/ Part 2 - Learn 82.7% of Assembly Language in About Three Pages http://atariage.com/forums/blog/576/entry-13176-part-2-of-11-simple-assembly-for-atari-basic/ Part 3 - The World Inside a USR() Routine http://atariage.com/forums/blog/576/entry-13177-part-3-of-11-simple-assembly-for-atari-basic/ Part 4 - Implement DPEEK() http://atariage.com/forums/blog/576/entry-13178-part-4-of-11-simple-assembly-for-atari-basic/ Part 5 - Implement DPOKE http://atariage.com/forums/blog/576/entry-13180-part-5-of-11-simple-assembly-for-atari-basic/ Part 6 - Various Bit Manipulations http://atariage.com/forums/blog/576/entry-13181-part-6-of-11-simple-assembly-for-atari-basic/ Part 7 - Convert Integer to Hex String http://atariage.com/forums/blog/576/entry-13182-part-7-of-11-simple-assembly-for-atari-basic/ Part 8 - Convert Integer to Bit String http://atariage.com/forums/blog/576/entry-13183-part-8-of-11-simple-assembly-for-atari-basic/ Part 9 - Memory Copy http://atariage.com/forums/blog/576/entry-13184-part-9-of-11-simple-assembly-for-atari-basic/ Part 10 - Binary File I/O Part 1 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13185-part-10-of-11-simple-assembly-for-atari-basic/ Part 11 - Binary File I/O Part 2 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13186-part-11-simple-assembly-for-atari-basic-the-end/ ============================================================== Writing a two-byte, 16-bit value (DPOKE) goes hand-in-hand with reading (DPEEK). An 8-bit computer environment has many opportunities for managing 16-bit address and the Atari environment is no exception. Address of display lists, Address of screen memory. Addresses of buffers. Address pointers abound. OSS BASIC XL provides the DPOKE command to perform a 16-bit, two-byte write to a specified memory location. Like DPEEK, this can be duplicated in regular Atari BASIC, although much slower. So, in BASIC XL the action: Dpoke Address, Value is frequently seen in Atari BASIC programs expressed as: POKE ADDRESS, VALUE - INT( VALUE / 256 ) * 256 POKE ADDRESS + 1, INT( VALUE / 256 ) That’s a bit more complicated than DPEEK. Here the value must be broken down into the high byte (integer result of the value divided by 256) and the low byte (the remainder after division), and then the two values assigned to the two consecutive bytes in memory in low byte, high byte order. This can also be worked into a reusable subroutine in Atari BASIC: 10 DPOKE=28500 20 ADDRESS=560:VALUE=39968:GOSUB DPOKE 30 END. . . 28497 REM IMPLEMENT DPOKE 28498 REM INPUT = ADDRESS 28499 REM INPUT = VALUE 28500 HB=INT(VALUE/256) 28501 POKE ADDRESS,VALUE-HB*256:POKE ADDRESS+1,HB 28502 RETURN This is still fairly simple code. A small difference here is the high byte is calculated and retained to avoid multiple occurrences of the same floating point division. But, again, expressed in Atari BASIC this is fairly slow using one floating point division and one multiplication. As before, applying assembly language rocketry yields: DPOKE in Mac/65 Assembler Code 0100 ; DPOKE 0105 ; 0110 ; Write a 16-bit word into 0115 ; the specified address. 0120 ; 0125 ; USR 2 arguments: 0130 ; Addr == address to write. 0135 ; Val == Value to write. 0140 ; 0145 ; USR return value is 0. 0150 ; 0155 ; Use the FR0 FP register. 0160 ; The return value for BASIC 0165 ; goes in FR0. 0170 ; No FP is used so all of FR0 0175 ; (and more FP registers) can 0180 ; be considered available. 0185 ; 0190 ZRET = $D4 ; FR0 $D4/$D5 Return value 0195 ZARGS = $D5 ; $D6-1 for arg Pulldown loop 0200 ZVAL = $D6 ; FR0 $D6/$D7 Value 0205 ZADDR = $D8 ; FR0 $D8/$D9 Address 0210 ; 0215 .OPT OBJ 0220 ; 0225 ; Arbitrary. This is relocatable. 0230 ; 0235 *= $9600 0240 ; 0245 INIT 0250 PLA ; argument count 0255 TAY 0260 BEQ EXIT ; shortcut for no args 0265 ASL A ; now number of bytes 0270 TAY 0275 CMP #$04 ; Value, Address 0280 BEQ PULLDOWN 0285 ; 0290 ; Bad args. Clean up for exit. 0295 ; 0300 DISPOSE ; any number of args 0305 PLA 0310 DEY 0315 BNE DISPOSE 0320 BEQ EXIT 0325 ; 0330 ; This code works the same 0335 ; for 1, 4, 8 ... arguments. 0340 ; 0345 PULLDOWN 0350 PLA 0355 STA ZARGS,Y 0360 DEY 0365 BNE PULLDOWN 0370 ; 0375 ; Y is already zero here. 0380 ; 0385 LDA ZVAL 0390 STA (ZADDR),Y 0395 INY ; now Y is 1 0400 LDA ZVAL+1 0405 STA (ZADDR),Y 0410 ; 0415 ; On failure Y = 0 0420 ; On Success Y = 1 0425 ; 0430 EXIT ; 0435 STY ZRET 0440 STY ZRET+1 0445 RTS ; bye. 0450 ; 0455 .END The error detection and handling have a few differences from DPEEK. The DPOKE routine does not inherently require a response value where DPEEK does, so the DPOKE code does not have separate, early-exit code and uses only one place to set the response value merely to indicate general success or failure. This code uses an additional Page Zero value to accept the value to write to the address: 0190 ZRET = $D4 ; FR0 $D4/$D5 Return value 0195 ZARGS = $D5 ; $D6-1 for arg Pulldown loop 0200 ZVAL = $D6 ; FR0 $D6/$D7 Value 0205 ZADDR = $D8 ; FR0 $D8/$D9 Address The real work part of the code is obviously different since writing bytes into memory (DPOKE) is not the same as reading bytes from memory (DPEEK): 0375 ; Y is already zero here. 0380 ; 0385 LDA ZVAL 0390 STA (ZADDR),Y 0395 INY ; now Y is 1 0400 LDA ZVAL+1 0405 STA (ZADDR),Y The DPOKE version above conforms to my standard plan (more or less) for stack management and error handling. Most of the remaining utilities will stick with this plan. However, for the sake of (averting) argument there are admittedly more optimal ways to accomplish this depending on the programmer's definition of optimal. For instance, the value to write to the address need not be temporarily stored in Page Zero. After the code pulls the target address from the stack and establishes it in Page Zero it could pull the value and directly store it to the target address, like this: 0375 ; Y must be 1 here. 0380 ; 0385 PLA ; high byte 0390 STA (ZADDR),Y 0395 DEY ; now Y is 0 0400 PLA ; low byte 0405 STA (ZADDR),Y That does look clever as it subtracts two bytes from the code, and removes ZVAL from Page Zero. However, to make this work the argument handling must be different to pull two, not four bytes worth of arguments, and the Y value must be reset to 1 before arriving at this code, and the error return value needs to be reworked. In other words, the code savings in one place may be consumed by code needed in another place. The standardized approach may not be the most optimal, but it is conceptually consistent, flexible, and doesn't need to be re-factored from scratch for each different routine. Also, regardless of the degree of inefficiency in this less-than-optimal method the execution time is negligible compared to slower Atari BASIC. More time is spent by Atari BASIC converting the floating point values into 16-bit integers to pass as arguments to the machine language routine than the amount of time for execution of the the machine language routine. Testing DPEEK and DPOKE The Atari BASIC program below, TESTDPK.BAS, demonstrates how to load the machine language routines into strings and it tests the two routines: 0 REM H1:TESTDPK.BAS 100 REM TEST DPEEK AND DPOKE USR() TOOLS 110 GRAPHICS 0:POKE 710,0:POKE 82,0 120 GOSUB 10000 130 REM 140 REM TEST DPEEK 150 REM 155 ? "*** DPEEK ***" 160 DL=PEEK(560)+256*PEEK(561) 170 ? "BASIC Dpeek Display List = ";DL 180 DL=0 190 DL=USR(DPEEK,560) 200 ? "USR() Dpeek Display List = ";DL 210 REM 220 REM TEST DPOKE 230 REM 240 CL=10+256*68:REM 711=10,712=68 250 X=USR(DPOKE,711,CL) 260 REM 270 REM VERIFY WITH DPEEK 280 REM 290 ? "*** DPOKE ***" 300 ? "10 + 256 * 68 = ";10+256*68 310 CL=PEEK(711)+256*PEEK(712) 320 ? "BASIC After Dpoke = ";CL 330 CL=0 340 CL=USR(DPEEK,711) 350 ? "USR() After Dpoke = ";CL 360 END 970 REM 980 REM DPEEK AND DPOKE 990 REM 10000 DIM DPE$(36),DPO$(38) 10001 DPEEK=ADR(DPE$):DPOKE=ADR(DPO$) 10010 RESTORE 27000 10011 FOR I=0 TO 35 10100 READ D:POKE DPEEK+I,D 10101 NEXT I 10110 FOR I=0 TO 36 10111 READ D:POKE DPOKE+I,D 11000 NEXT I 11001 RETURN 26999 REM dpeek.obj 27000 DATA 104,168,240,10,10,168,201,2 27010 DATA 240,9,104,136,208,252,132,212 27020 DATA 132,213,96,104,153,213,0,136 27030 DATA 208,249,177,214,133,212,200,177 27040 DATA 214,133,213,96 28999 REM dpoke.obj 29000 DATA 104,168,240,28,10,168,201,4 29001 DATA 240,6,104,136,208,252,240,16 29002 DATA 104,153,213,0,136,208,249,165 29003 DATA 214,145,216,200,165,215,145,216 29004 DATA 132,212,132,213,96 First the program performs DPEEK the slow way in BASIC by calculating the 16-bit address of the display list (at locations 560 and 561) with a multiply in BASIC (line 160). Then the program performs the same action using the DPEEK USR() routine (line 190). The value determined by each method is displayed for comparison: Next the program tests the DPOKE USR() routine by assigning a 16-bit value to locations 711 and 712. First it calculates the 16-bit value and reports it. Then it performs DPOKE via the USR() routine (Line 250). Then it retrieves the contents of the two memory locations by the slower BASIC method for DPEEK and by the DPEEK USR() routine. Finally, it reports the value determined by each method to verify the value was written to memory correctly: Below are the source files and examples of how to load the machine language routine into BASIC included in the disk image and archive: DPOKE File List: DPOKE.M65 Saved Mac/65 source DPOKE.L65 Mac/65 source listing DPOKE.T65 Mac/65 source listed to H6: (linux) DPOKE.ASM Mac/65 assembly listing DPOKE.TSM Mac/65 assembly listing to H6: (linux) DPOKE.OBJ Mac/65 assembled machine language program (with load segments) DPOKE.BIN Assembled machine language program without load segments DPOKE.LIS LISTed DATA statements for DPOKE.BIN routine. DPOKE.TLS LISTed DATA statements for DPOKE.BIN routine to H6: (linux) MAKEDPOK.BAS BASIC program to create the DPOKE.BIN file. This also contains the DPOKE routine in DATA statements. MAKEDPOK.LIS LISTed version of MKDPOKE.BAS MAKEDPOK.TLS LISTed version of MKDPOKE.BAS to H6: (linux) TESTDPK.BAS BASIC program that tests the DPEEK and DPOKE USR() routines. TESTDPK.LIS LISTed version of TESTDPK.BAS. TESTDPK.TLS LISTed version of TESTDPK.BAS to H6: (linux) ZIP archive of files: Dpoke_Disk.zip Tar archive of files (remove the .zip after download) Dpoke_Disk.tgz.zip For we are his workmanship, created in Christ Jesus for good works, which God prepared beforehand, that we should walk in them. Ephesians 2:10
  12. DPEEK ============================================================== Part 1 - Introduction http://atariage.com/forums/blog/576/entry-13175-part-1-of-11-simple-assembly-for-atari-basic/ Part 2 - Learn 82.7% of Assembly Language in About Three Pages http://atariage.com/forums/blog/576/entry-13176-part-2-of-11-simple-assembly-for-atari-basic/ Part 3 - The World Inside a USR() Routine http://atariage.com/forums/blog/576/entry-13177-part-3-of-11-simple-assembly-for-atari-basic/ Part 4 - Implement DPEEK() http://atariage.com/forums/blog/576/entry-13178-part-4-of-11-simple-assembly-for-atari-basic/ Part 5 - Implement DPOKE http://atariage.com/forums/blog/576/entry-13180-part-5-of-11-simple-assembly-for-atari-basic/ Part 6 - Various Bit Manipulations http://atariage.com/forums/blog/576/entry-13181-part-6-of-11-simple-assembly-for-atari-basic/ Part 7 - Convert Integer to Hex String http://atariage.com/forums/blog/576/entry-13182-part-7-of-11-simple-assembly-for-atari-basic/ Part 8 - Convert Integer to Bit String http://atariage.com/forums/blog/576/entry-13183-part-8-of-11-simple-assembly-for-atari-basic/ Part 9 - Memory Copy http://atariage.com/forums/blog/576/entry-13184-part-9-of-11-simple-assembly-for-atari-basic/ Part 10 - Binary File I/O Part 1 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13185-part-10-of-11-simple-assembly-for-atari-basic/ Part 11 - Binary File I/O Part 2 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13186-part-11-simple-assembly-for-atari-basic-the-end/ ============================================================== Since one byte can hold the value 0 to 255 a value larger than 255 requires two bytes. The second byte takes the place value 256. Just as Base10 has “ones” values 0 to 9, and then “tens” value for the next position, so the two bytes provide a Base256 value – “ones” value 0 to 255 in the first position and then 256 as the “tens” in the second position. The two-digit value limit for Base10: multiply the maximum base value by 10 for the “tens” value and add the maximum base value of the “ones”, or 9 * 10 + 9 = 99. The same applies for Base256: multiply the maximum base value by 256 for the “tens” and then add the maximum base value as the “ones”, or 255 * 256 + 255 == 65,535. (Or, in hex this is $FF * $100 + $FF == $FFFF.) So, the real value limit of 16-bits starts at 0 and ends at 65,535 or $FFFF. The 64K address space of an 8-bit computer is described by a 16-bit value. So, the Atari environment is liberally sprinkled with 16-bit values as addresses, pointers, and larger integer values. Manipulating 16-bit values is complementary to working in the Atari computing environment, but Atari BASIC does not provide a direct and easy method for this. OSS BASIC XL provides the Dpeek() function to perform a 16-bit, two-byte PEEK of the value at a specified memory location. This can also be duplicated in regular Atari BASIC, although slower. In BASIC XL the action: Value = Dpeek( Address ) is frequently seen in Atari BASIC programs expressed as: VALUE = PEEK( ADDRESS ) + 256 * PEEK( ADDRESS + 1 ) That’s not very complicated. A little programming grease mixed with Atari BASIC’s ability to GOSUB to a variable produces this reusable subroutine: 10 DPEEK=28000 20 ADDRESS=560:GOSUB DPEEK 30 ? "DPEEK(560)= ";VALUE 40 END. . . 27997 REM IMPLEMENT DPEEK 27998 REM INPUT = ADDRESS 27999 REM OUTPUT = VALUE 28000 VALUE=PEEK(ADDRESS)+256*PEEK(ADDRESS+1) 28001 RETURN The routine is simple; just one real line of BASIC code. But, execution in Atari BASIC is fairly slow, since it includes a floating point multiplication. Infrequent slothfulness is forgivable, but repeated use causes obvious performance drag. What this problem needs is a little assembly language propulsion... DPEEK Mac/65 Assembler Code 0100 ; DPEEK 0105 ; 0110 ; Return the 16-bit contents 0115 ; at the specified address. 0120 ; 0125 ; USR 1 arguments: 0130 ; Addr == address of value. 0135 ; 0140 ; USR return value is 16-bit 0145 ; contents of address. 0150 ; 0155 ; Use the FR0 FP register. 0160 ; The return value for BASIC 0165 ; goes in FR0. 0170 ; No FP is used so all of FR0 0175 ; (and more FP registers) can 0180 ; be considered available. 0185 ; 0190 ZRET = $D4 ; FR0 $D4/$D5 Return value 0195 ZARGS = $D5 ; $D6-1 for arg Pulldown loop 0200 ZADDR = $D6 ; FR0 $D6/$D7 Address 0205 ; 0210 .OPT OBJ 0215 ; 0220 ; Arbitrary. This is relocatable. 0225 ; 0230 *= $9700 0235 ; 0240 INIT 0245 PLA ; argument count 0250 TAY 0255 BEQ EXIT_ERR ; shortcut for no args 0260 ASL A ; now number of bytes 0265 TAY 0270 CMP #$02 ; Address 0275 BEQ PULLDOWN 0280 ; 0285 ; Bad args. Clean up for exit. 0290 ; 0295 DISPOSE ; any number of args 0300 PLA 0305 DEY 0310 BNE DISPOSE 0315 ; 0320 EXIT_ERR ; return "error" 0325 STY ZRET ; Y is Zero 0330 STY ZRET+1 ; 0335 RTS ; bye. 0340 ; 0345 ; This code works the same 0350 ; for 1, 4, 8 ... arguments. 0355 ; 0360 PULLDOWN 0365 PLA 0370 STA ZARGS,Y 0375 DEY 0380 BNE PULLDOWN 0385 ; 0390 ; Y is already zero here. 0395 ; 0400 LDA (ZADDR),Y 0405 STA ZRET 0410 INY 0415 LDA (ZADDR),Y 0420 STA ZRET+1 0425 ; 0430 RTS ; bye. 0435 ; 0440 .END In this first example I’ll walk through all the setup and safety checks. This will be similar for most of the other utilities. Some programmers would consider that there is much more code here than required. The safety checks look like overkill, because the working code for this particular utility is so short. Protecting the BASIC programmer from torpedoing the system is important enough to justify protection. From the point of view of execution timing this overhead is inconsequential compared to the speed of BASIC. First of all, the routine is relocatable which means the code makes no absolute references to locations within itself. All branches are relative to the current location, so the code could execute from almost anywhere in memory. However, it does need a couple of fixed locations for working values in the program: 0155 ; Use the FR0 FP register. 0160 ; The return value for BASIC 0165 ; goes in FR0. 0170 ; No FP is used so all of FR0 0175 ; (and more FP registers) can 0180 ; be considered available.0185 ; 0190 ZRET = $D4 ; FR0 $D4/$D5 Return value 0195 ZARGS = $D5 ; $D6-1 for arg Pulldown loop 0200 ZADDR = $D6 ; FR0 $D6/$D7 Address The program needs two values – the address from which to PEEK the 16-bit value, and a place to put the 16-bit value for BASIC to reference. Specifically, the code is using the Page Zero locations. “Page Zero” means all the address locations where the high byte of the address is $00. (The 256 locations from address $0000 to $00FF). Page Zero locations are chosen for several reasons: 6502 instructions referencing Page Zero are one byte shorter and usually execute faster than instructions referencing other pages. The 6502 has useful addressing modes that only work for Page Zero references. BASIC already defines a place in Page Zero for the value the machine language routine returns to BASIC. Since Page Zero locations are so useful they are also highly contested. The Atari OS defines and uses just about every byte in the first half of Page Zero. BASIC and the Floating Point routines use almost all of the second half of Page Zero. As it turns out the locations used by this machine language routine are “claimed” by the Floating Point routines. However, as long as the machine language routine does not need to use the Floating Point routines then these locations are free for use. Now to the working code. The routine begins by pulling the argument count from the stack and checking for zero arguments. If it finds no arguments to process then the routine branches to another location for an early exit. Yes, the TAY instruction is not needed to correctly branch for no arguments. The PLA instruction sets the zero flag when the argument count popped from the stack is zero. However, the exit code will use the Y register to return an error value (which is zero) to BASIC: 0240 INIT 0245 PLA ; argument count 0250 TAY 0255 BEQ EXIT_ERR ; shortcut for no args Next, the routine converts the number of arguments into the number of bytes to pull from the stack. It uses ASL which is the same as multiplying the value in the Accumulator times two. This is stored in the Y register which is used as an index for later loops. The routine verifies the argument count is correct (only 1 argument – the address to PEEK) which is 2 bytes. If this is correct the routine branches to the code that will pull the stack values and place them in Zero page memory for use later: 0260 ASL A ; now number of bytes 0265 TAY 0270 CMP #$02 ; Address 0275 BEQ PULLDOWN If the argument count is incorrect then the routine discards the argument values on the stack, so it can safely exit. Remember the Y register already contains the number of argument bytes on the stack: 0295 DISPOSE ; any number of args 0300 PLA 0305 DEY 0310 BNE DISPOSE Next, the routine falls into the exit section. Earlier, if there were no arguments the routine branched here directly. Note that if there are no arguments the Y register contained 0 when it branched here directly, and at the conclusion of cleaning up the stack in the DISPOSE loop the Y register will also be 0. So, in either case of bad arguments, too many or too few, the return value to BASIC is cleared to 0: 0320 EXIT_ERR ; return "error" 0325 STY ZRET ; Y is Zero 0330 STY ZRET+1 ; 0335 RTS ; bye. Clearing the return value really isn’t necessary and isn’t exceedingly useful beyond insuring random values are not returned to BASIC in the case of an error. Returning a real error would require the USR() pass another argument from BASIC that the routine would use to indicate success or failure. However, the DPEEK action is so simple that this level of error detection begins to enter the arena of silly. The error detection shown here already leans toward overkill and is done for design consistency with the other utilities covered later. At this point the arguments are correct. The routine pulls the values from the stack and places them in Page Zero locations $D6/$D7 referred to as ZADDR. On entry to this point in the code the Y register contains the number of bytes to pull from the stack (always 2 for this routine). The loop pulls them off the stack and places them into memory descending as it goes, because the high byte is pulled first, then the low byte. The base address is not ZADDR, but ZARGS, a value defined one byte less than ZADDR. This is because the Y value will be used as an index from the number of argument bytes (2, 4, 6, etc.) counting down to 1, not to 0. Counting backwards results in the stack values placed in memory in the correct low byte, high byte order used by the 6502. When Y reaches 0 it falls out of the loop: 0360 PULLDOWN 0365 PLA 0370 STA ZARGS,Y 0375 DEY 0380 BNE PULLDOWN This code works for any number of arguments as long as the destination can be sequential bytes in memory. However, this is admittedly overkill for only one argument. More explicit code that directly pulls one 16-bit argument from the stack requires the same number of instructions, but is one byte less (and executes faster). Just for reference: 0360 PULLDOWN 0365 PLA 0370 STA ZADDR+1 ; high byte first. 0375 PLA 0380 STA ZADDR ; low byte next. The actual work to perform the double byte peek is just the five instructions before the final RTS. The routine reads two bytes through the address contained in ZADDR ($D6/$D7) and copies the bytes to the return value, ZRET ($D4/$D5): 0400 LDA (ZADDR),Y 0405 STA ZRET 0410 INY 0415 LDA (ZADDR),Y 0420 STA ZRET+1 After the routine exits Atari BASIC converts the value stored in locations $D4/$D5 (ZRET in the code) into a floating point value. This becomes the return value from USR(). So, in the following BASIC statement the variable X is assigned the value taken from $D4/$D5: 250 X=USR(DPEEK,560): REM DISPLAY LIST ADDRESS Below are source files and examples of how to load the machine language routine into BASIC included in the disk image and archive: DPEEK File List: DPEEK.M65 Saved Mac/65 source DPEEK.L65 Mac/65 source listing DPEEK.T65 Mac/65 source listed to H6: (linux) DPEEK.ASM Mac/65 assembly listing DPEEK.TSM Mac/65 assembly listing to H6: (linux) DPEEK.OBJ Mac/65 assembled machine language program (with load segments) DPEEK.BIN Assembled machine language program without load segments MKDPEEK.BAS BASIC program to create the DPEEK.BIN file. This also contains the DPEEK routine in DATA statements. MKDPEEK.LIS LISTed version of MKDPEEK.BAS MKDPEEK.TLS LISTed version of MKDPEEK.BAS to H6: (linux) DPEEK.BAS BASIC program that loads DATA statements into a string. DPEEK.LIS LISTed version of DPEEK.BAS DPEEK.TLS LISTed version of DPEEK.BAS to H6: (linux) The next task should be a test program to demonstrate using DPEEK in BASIC. But, since DPEEK isn’t much use without DPOKE, we will visit DPOKE first before using the machine language routines together in a BASIC program. ZIP archive of files: Dpeek_Disk.zip Tar archive of files (remove the .zip after download) Dpeek_Disk.tgz.zip Therefore I tell you, do not be anxious about your life, what you will eat or what you will drink, nor about your body, what you will put on. Is not life more than food, and the body more than clothing? Look at the birds of the air: they neither sow nor reap nor gather into barns, and yet your heavenly Father feeds them. Are you not of more value than they? And which of you by being anxious can add a single hour to his span of life? And why are you anxious about clothing? Consider the lilies of the field, how they grow: they neither toil nor spin, yet I tell you, even Solomon in all his glory was not arrayed like one of these. Matthew 6:25-34
  13. While I was browsing some old source code, I frequently stumbled upon MAC/65 tokenized files. Being too lazy to repeatedly start an emulator to convert them to (AT)ASCII and being unable to find a program online to detokenize them, I set out to write such a program myself. With some luck, I found a description of the format in the form of an old Analog Computing article. After that, it was pretty straightforward. Here's the source. Compile with gcc -W -Wall -O3 -o demac65 demac65.c. If you want line numbers, uncomment the printf statement. If you want all lowercase, there's tr(1). Have fun demac65.c.gz
  14. If anyone is wanting to get started in Assembler the first part of my "How Not to Learn Assembler" column is in the new issue of Pro(c) Atari Magazine. It has a focus on game development and will cover beginner to intermediate topics, those more advanced may want to shout at the magazine as they read it but may get a laugh or two! If anyone wants to write anything covering anything at all, please send it to me for the next issue. Thanks, Jason
  15. I use MAC/65 Assembler on the Atari. This time I have a GR.7 screen from MAD Studio, it supplies the data file and the BASIC source code to load this which works fine: 10 GRAPHICS 7+16 20 CLOSE #1 30 OPEN #1,4,0,"H1:SCREEN01.GR7" 40 SCR=PEEK(88)+PEEK(89)*256 50 FOR I=0 TO 3839 60 GET #1,BYTE 70 POKE SCR+I,BYTE 80 NEXT I ... MAC/65 will not import (enter) the SCREEN01.GR7 file, tried various modes, also tried converting end of line using Memopad. Sounds like it goes through the motions but nothing, or truncated record if EOL changed - guess as one big block of data. For a long winded workaround, I loaded the file in BASIC, did a Binary Save from DOS, and then I have someone else's BASIC utilis that convert this to a string and then to data statements -or - I could use my Byte Buster Utility I did also try a BLOAD in MAC/65 which brings the data in ok, but this is only from the command prompt (not part of the assemble) Is there another file conversion utility? or I did ponder if it was possible to load a dummy data file into assember, then bload the screen01.gr7 somewhere else in RAM and then write a program to copy the bytes over the existing data statements - then I could save the file? or some other (better) way of doing this e.g. in BASIC? Hope this makes sense Thanks
  16. Hello together! Would like to start a separate topic for MAC/65 out of a discussion within the ACTION! source code topic. As of 2015 to my knowledge there are 3 carts and 2 disk versions of MAC/65 available: - MAC/65 Macro Assembler (disk|cart) version, year : 2.00, 1982 (disk) version, year : 4.20, 1982 (disk) version, year : 1.02, 1984 (black cart?) version, year : 1.01, 1984 (black cart) version, year : 1.00, 1984 (orange cart) ; I have an orange cart with 1.01 myself author/company : Stephen D. Lawrow, Jim Dunion, OSS, Inc. available..... : 1982-1984 and up to 1994 package....... : assembler, editor, monitor, manual features...... : macros, source include Two pass 6502 assembler with integrated editor/monitor. Mac/65 is a direct descendant of the Atari Assembler/ Editor (via EASMD). Source: http://en.wikipedia.org/wiki/MAC/65 ACTION!, Basic XE, Basic XL and Mac/65 were sold to Tom Harker at ICD. From another topic concerning the OSS Newsletters: Fall 1986 page 13: " OSS Newsletter Fall 1986 -------------- --------- A: Version 1.01 and 1.02 are fixed to produce the proper code (PS - 1.02 is the latest version). For update information see the update info sheet. " Further: " Mike distributed one of the disk based versions of SD and disk based MAC/65 with DDT as freeware. " Source: https://groups.google.com/forum/#!msg/comp.sys.atari.8bit/wWxsqsmVcVI/z6W0y0U8eHEJ Does anyone have that freeware version, else one with DDT? Stephen is a really good soul: https://groups.google.com/forum/#!msg/comp.sys.atari.8bit/wWxsqsmVcVI/z6W0y0U8eHEJ https://groups.google.com/forum/#!msg/comp.sys.atari.8bit/TVwmswjj2zI/Z_8TwOXPIMsJ MD5 checksum of the 1.02 rom version, the real McCoy! : b560d57e36c9a4b95e6fd296937148d5 Same for the 1.02 car version : a67098f4476c95c778396f7cd269a24e Further: Type 3: OSS '034M' 16 KB cartridge There are two types of OSS cartridges. Both are 16 KB and occupy 8 KB of address space between $A000 and $BFFF. The cartridge memory is divided into 4 banks, 4 KB each. One bank ('main') is always mapped to $B000-$BFFF. The other 3 banks are mapped to $A000-$AFFF. The current bank is selected by accessing a byte in $D500-$D5FF. Only 4 lowest bits of address are significant. The '034M' scheme is the more complicated one. The main bank is D. An access to: · $D5x0 or $D5x1 selects bank A. · $D5x3 or $D5x7 selects bank B. · $D5x4 or $D5x5 selects bank C. · $D5x2 or $D5x6 is not useful. It disables ROM (there're $FF bytes in $A000-$AFFF). · $D5x8-$D5xF disables whole cartridge (enables computer's memory in address space between $A000 and $BFFF). Type 15: OSS 'M091' 16 KB cartridge This is the simpler one of OSS schemes. It uses only A0 and A3 address lines: · A3=0, A0=0 - $A000-$AFFF: bank B, $B000-$BFFF: bank A · A3=0, A0=1 - $A000-$AFFF: bank D, $B000-$BFFF: bank A · A3=1, A0=0 - disable cartridge · A3=1, A0=1 - $A000-$AFFF: bank C, $B000-$BFFF: bank A Bugs: There is a bug in the MAC/65 cartridge which gives an error on a JSR or JMP to a zero page address. A temporary fix would be to use macros in the source code. As examples, try these: 1000 .MACRO @JSR 1010 .BYTE $20 1020 .WORD %1 1030 .ENDM 1040 .MACRO @JMP 1050 .BYTE $4C 1060 .WORD %1 1070 .ENDM To invoke the macro, type @JSR (or @JMP) in place of the JSR or JMP instruction, and the proper hex code will be generated. From OSS newsletter (summer 1983): Comparision of 1.01 and 1.02 carts: 00000009: CA CC 0000000E: 94 92 00000022: 8D BD 00000023: 00 4E 00000024: D5 03 00000025: A6 C9 00000026: 08 04 00000027: F0 D0 00000028: 07 02 00000029: A5 A9 0000002A: 83 02 0000002B: D0 8D 0000002C: 03 8F 0000002D: 4C 04 0000002E: D5 60 0000002F: BE 00 00000030: D8 8D 00000031: 58 00 00000032: A2 D5 00000033: FF A6 00000034: 9A 08 00000035: 86 F0 00000036: 11 07 00000037: E8 A5 00000038: 86 83 00000039: A7 D0 0000003A: 86 03 0000003B: A6 4C 0000003C: 86 D5 0000003D: A2 BE 0000003E: 20 D8 0000003F: BF 58 00000040: B7 A2 00000041: A2 FF 00000042: 15 9A 00000044: BF EE 00000045: B7 AF 000006B2: A9 A0 000006B4: 9D B9 000006B5: 4E 8D 000006B6: 03 04 000006B7: AD 9D 000006B8: 8D 4E 000006B9: 04 03 000006BA: 9D CA 000006BB: 4C 88 000006BC: 03 10 000006BD: AD F6 000006BE: 8E E8 000006BF: 04 E8 000006C0: 9D E8 000006C1: 4D EA 000006C2: 03 EA 00000BBF: 4D 22 00000BC0: B6 B0 00000FFA: 22 30 00001014: B1 B2 00001016: 63 43 00001FEE: 00 86 00001FEF: 00 11 00001FF0: 00 E8 00001FF1: 00 86 00001FF2: 00 A2 00001FF3: 00 86 00001FF4: 00 A6 00001FF5: 00 86 00001FF6: 00 A7 00001FF7: 00 20 00001FF8: 00 BF 00001FF9: 00 B7 00001FFA: 00 A2 00001FFB: 00 15 00001FFC: 00 4C 00001FFD: 00 BF 00001FFE: 00 B7 000022C4: AA F0 000022C5: BB AF 00002FF0: 00 20 00002FF1: 00 AA 00002FF2: 00 BB 00002FF3: 00 AD 00002FF4: 00 8F 00002FF5: 00 04 00002FF6: 00 D0 00002FF7: 00 05 00002FF8: 00 A9 00002FF9: 00 02 00002FFA: 00 8D 00002FFB: 00 8D 00002FFC: 00 04 00002FFD: 00 60 ROM-Images: Atari 400 800 XL XE MAC/65 : scans, dump, download, screenshots, ads, videos, catalog, instructions, roms ATR-Image: MAC-65 2.00 and 4.20 with Bug-65 2.0 and DOS XL 2.30.atr Images from the different versions: Hope, that there is someone out there in the galaxy who can help to make this the final chapter?
  17. Hello together, Today is a very special day, because a long, long search and work around the world with many users is now over with a final good end after decades. The community now has the lost to be believe, final OSS supercart of the highest developed macro assembler for the Atari, the OSS MAC/65, MAC XL and MAC XE in various versions and this time with the source code together! Did I mention the additional commands like PHX, PHY and so on? We are very happy about that, because now, we can make a final macro assembler for the Atari, let's call it: Ultimate MAC/65? Further, we can interbreed with other source codes, for example ACTION! for an enhanced editor (scrolling) or the EXTEND command from the source code of BASIC XE to get used of the additional RAM with now up to 4 MB! All this is now possible and to your feet. All links to the software can be found here: https://atariwiki.org/wiki/Wiki.jsp?page=Mac65 Enjoy and have fun. :-))) This service was brought to you by Kevin, a good soul (who would like to stay anonymously), JAC!, Tomasz 'KrOtki' Krasuski, a still unknown donator of the hex codes, which enabled us to restore the code, the files and later from that, finally, the cart itself. For those of you, who may ask about the unknown donator: in the very last picture of the microfilm there was a slide with the following sentence: After digitizing, destroy everything send to you and there will be more! I did exactly as ordered, and guess what, we can continue in part 3! Not kidding. Check back in here again. :-) Call for help, up to now, we have some problems with assembling, just 4 errors are remaining: Who can help us to get them lost? The BIN-files just run in Altirra with OSS '043M' ; who can create a '034M'-version? :-) MAC_XE_v3.4i_(1986-02-11)(Lawrow,_Stephen)(US).bin MAC-65_v3.6_(1988-01-07)(Lawrow,_Stephen)(US).bin
  18. Hi everyone, Just a short question. On my list is a file called: OSS MAC/65 MCDO.M65 file. Does anyone know of that file or where it belongs to? I am totally lost and can't remember. Thank you very much in advance.
  19. I'm looking for a couple of utility disks that don't seem to be in any of the archives. QuickCode is a set of macros for use with Mac/65: QuickCode - Atarimania FlashBack is a backup program for use with SpartaDOS: FlashBack - Atarimania Info for both can also be found here: Antic Reviews - Power Tools If anybody has an ATR of either of these to share, or has a real copy and would be so kind as to make an image, it would be greatly appreciated. Thanks, MF
  20. Ok Assembler fans... As you may be aware I've been adding levels to the Snowplow game (NYD2017) from analog mag #64. Please can someone take a look at the assembly code (MAC/65 & commented, listed in the mag also) and see why the game only loads two extra levels before returning to the inbuilt first level which is hard coded. As far as I read it should support multiple level files. Comments say it will load files in disk order. Looks like it does a directory using CIO. I noticed on line 8530 in the GETDIR routine it has a CMP #'F which I thought was a typo, but it's in the magazine listing too. " ' F " looks like it assembles as $46/#70 which is "ZDRVA" in mapping the atari - if this is any help. It's checking something in the buffer against this and then going to the inbuilt level or not. Was this a typo or something clever? SNOWPLOW-TESTING.ATR On the DOS disk attached is code: SNOW.PT1 AND .PT2 game: snowplow.com test levels: SMAP.A-F, which have 1 blob of scenery to represent the level # and a short road so you can complete them quickly You need to complete the first screen to reach these. The game returns to the title screen after each level and you need to press start to load the next one (providing you have a man left) The first (inbuilt) level starts with 180 fuel and this drops by 10 on each subsequent level. Thanks Jason
  21. The World Inside a USR() Routine ============================================================== Part 1 http://atariage.com/forums/blog/576/entry-13175-part-1-of-11-simple-assembly-for-atari-basic/ Part 2 http://atariage.com/forums/blog/576/entry-13176-part-2-of-11-simple-assembly-for-atari-basic/ Part 3 http://atariage.com/forums/blog/576/entry-13177-part-3-of-11-simple-assembly-for-atari-basic/ Part 4 http://atariage.com/forums/blog/576/entry-13178-part-4-of-11-simple-assembly-for-atari-basic/ Part 5 http://atariage.com/forums/blog/576/entry-13180-part-5-of-11-simple-assembly-for-atari-basic/ Part 6 http://atariage.com/forums/blog/576/entry-13181-part-6-of-11-simple-assembly-for-atari-basic/ Part 7 http://atariage.com/forums/blog/576/entry-13182-part-7-of-11-simple-assembly-for-atari-basic/ Part 8 http://atariage.com/forums/blog/576/entry-13183-part-8-of-11-simple-assembly-for-atari-basic/ Part 9 http://atariage.com/forums/blog/576/entry-13184-part-9-of-11-simple-assembly-for-atari-basic/ Part 10 http://atariage.com/forums/blog/576/entry-13185-part-10-of-11-simple-assembly-for-atari-basic/ Part 11 http://atariage.com/forums/blog/576/entry-13186-part-11-simple-assembly-for-atari-basic-the-end/ ============================================================== Atari BASIC's USR() function is powerful offering variable arguments and a return value. Many other BASICs' USR() or SYS() functions are relatively primitive taking no parameters and returning no results. This Atari-ism was a big help to me when learning C. The idea of subroutines called as functions with arguments and a return value was already introduced to me by Atari BASIC and USR(). Atari BASIC’s USR() function provides certain environmental conditions to a machine language routine. A machine language routine called via USR() requires good behavior conforming to the environment to insure safe execution and exit. A machine language routine meant to be called by USR() should follow these guidelines for acquiring arguments and returning to BASIC safely: The 6502 A, X, and Y registers do not need to be saved before use. The first byte on the stack provides the number of arguments passed to USR() excluding the first argument which is the starting address of the machine language routine. When there are no arguments this byte for the argument count will still be present on the top of the stack with the value 0. So, every machine language routine must always remove the argument count from the stack before the routine can exit safely. Every argument is present on the stack as two-byte, 16-bit integers. So, directly passing Atari BASIC's six-byte, floating point values is not supported. BASIC converts floating point values into 16-bit integers (truncating the fractional part) before pushing them on the stack. Likewise BASIC strings cannot be passed, but the addresses of strings determined by ADR() can be passed. Atari BASIC pushes the USR() arguments on the stack in order from right to left, so that the machine language routine will pop them off the stack in the same order as specified in the USR() statement. Atari BASIC pushes each argument value on the stack in low byte, high byte order. This is the opposite of how the 6502 pushes an address on the stack. So, the values must be pulled off the stack in reverse – high byte pulled off first, low byte second. The routine must remove all arguments from the stack before it can safely exit. The last item on the stack is the return address. The machine language program simply uses the RTS (Return from Subroutine) instruction to exit. The environment is in Binary Coded Decimal (BCD) mode for decimal math by default. If Add or Subtract instructions will be used on binary values then the routine must clear the decimal mode (CLD instruction). Decimal mode does not have be re-enabled before the routine exits. The machine language routine can return a result to Atari BASIC. The return value is a 16-bit integer stored at locations $D4/$D5 (or 212/213 decimal). In the USR() call X=USR(ADDRESS,...) BASIC converts the value at $D4/$D5 to an Atari floating point value and assigns it to the variable X. If a program needs multiple return values or other kinds of output, then additional arguments to the USR() routine can provide addresses as targets for output and it is up to the machine language routine how to use those addresses to return other values. A good machine language routine should be able to handle some adversity and protect against easy-to-manage, stupid-programmer tricks. For instance, a typo in a BASIC program could result in the program passing too many or too few parameters to USR(). This is an easy mistake to make, because BASIC can only verify the syntax of the USR() statement, not the number of parameters passed. A badly behaved routine would assume a specific number of parameters, and then cause the system to crash by returning with the stack in incorrect condition. Also, if a routine is not expected to return a computed output value to BASIC, then it is good form to use the return value as a flag indicating successful completion or failure of the routine. Next time, this assembly language discussion will include actual assembly language. Many are the plans in the mind of a man, but it is the purpose of the Lord that will stand. Proverbs 19:21
  22. Learn 82.7% of Assembly Language in About Three Pages ============================================================== Part 1 - Introduction http://atariage.com/forums/blog/576/entry-13175-part-1-of-11-simple-assembly-for-atari-basic/ Part 2 - Learn 82.7% of Assembly Language in About Three Pages http://atariage.com/forums/blog/576/entry-13176-part-2-of-11-simple-assembly-for-atari-basic/ Part 3 - The World Inside a USR() Routine http://atariage.com/forums/blog/576/entry-13177-part-3-of-11-simple-assembly-for-atari-basic/ Part 4 - Implement DPEEK() http://atariage.com/forums/blog/576/entry-13178-part-4-of-11-simple-assembly-for-atari-basic/ Part 5 - Implement DPOKE http://atariage.com/forums/blog/576/entry-13180-part-5-of-11-simple-assembly-for-atari-basic/ Part 6 - Various Bit Manipulations http://atariage.com/forums/blog/576/entry-13181-part-6-of-11-simple-assembly-for-atari-basic/ Part 7 - Convert Integer to Hex String http://atariage.com/forums/blog/576/entry-13182-part-7-of-11-simple-assembly-for-atari-basic/ Part 8 - Convert Integer to Bit String http://atariage.com/forums/blog/576/entry-13183-part-8-of-11-simple-assembly-for-atari-basic/ Part 9 - Memory Copy http://atariage.com/forums/blog/576/entry-13184-part-9-of-11-simple-assembly-for-atari-basic/ Part 10 - Binary File I/O Part 1 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13185-part-10-of-11-simple-assembly-for-atari-basic/ Part 11 - Binary File I/O Part 2 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13186-part-11-simple-assembly-for-atari-basic-the-end/ ============================================================== (The printed PDF version of this section really is just 3 pages long.) The hardest part of problem solving is overcoming the perception of difficulty. 6502 Assembly language is not difficult. It is only a different way of thinking about programs. In fact, because the 6502 is a simple processor, the world of 6502 Assembly language is simple. The hard part of Assembly programming is breaking a complex problem down in a way that can be solved by the simplicity of Assembly. BASIC presents a program as text and program execution means interpreting each BASIC instruction which takes considerable time. The 6502 world is distinctly different. The text representation of 6502 instructions that humans can read and write is called “Assembly Language”. An “Assembler” is a program to convert the human-readable Assembly Language text into the 6502 “Machine Language” instructions for execution. The final program is only the 6502 machine language instructions without the Assembly Language text. The 6502 works on data one, 8-bit byte at a time. The bytes of data come from memory which the 6502 describes with addresses 16-bits (two bytes) long. A two-byte address identifies one specific byte of memory at a location ranging from 0 to 65,535. This range is also referred to as 64K. The program the 6502 executes and the data it uses reside in the 64K of addressable memory. Additionally, Atari's custom hardware and devices also occupy specific addresses in memory. The 6502 has a 256 byte structure called the stack occupying a specific block of memory. The CPU accesses only the top of the stack. It may add (called pushing) bytes only at the top of the stack and can remove (called pulling) them only from the top of the stack. The 6502 uses the stack to save return addresses when calling subroutines. Programs may use it for temporary information storage. The 6502 has three dedicated registers called, “A” (for Accumulator), “X”, and “Y” that can each contain one byte of data. Most work occurs on data contained in these registers, though some operations can be done directly on memory. The majority of work takes place in the A register. The X and Y registers can't perform the same math and other data manipulations as the A register, but they can hold temporary values and facilitate looping behavior. Data may be exchanged one byte at a time between the A register and the X or Y registers, between the A register and the stack, and between any of the 3 registers and memory. The 6502 machine language program is a sequence of instructions in memory. The instructions direct work such as reading data from memory into a register, writing data from a register into memory, pushing and pulling values to and from the stack, performing addition or subtraction, comparing values, merging data values, manipulating individual bits in a byte, evaluating various kinds of true/false conditions, and changing program flow based on those evaluations. 6502 machine language instructions may be one, two, or three bytes long. In general, longer instructions take more time to execute than shorter instructions, though there are exceptions. At the completion of most instructions the CPU evaluates the results and sets flags identifying conditions for testing by subsequent instructions. The conditions include whether or not the result is zero, whether or not the result is negative, and whether or not a math operation results in a value overflow (or carry). The 6502 has special treatment for the first 256 bytes (aka a “page”) in memory referred to as Page Zero. The 6502 has specific instructions referencing Page Zero addresses that are shorter than other instructions, so Page Zero use can reduce the size of a program and in some cases makes a program faster. Page Zero addresses also facilitate special methods of accessing memory not possible with addresses outside of Page Zero. 6502 assembly language describes each instruction using three-character abbreviations followed by either an explicit byte value or an address. The “#” sign (that is the “pound” or “number” pre-internet, “hashtag” in contemporary terms) precedes explicit values to differentiate them from addresses. Values and memory addresses are expressed as decimal numbers (e.g. 32) or preceded with the dollar sign to express hexadecimal. e.g. the value #$20 equals #32 decimal, and the address $52 equals 82 decimal. Reading a value into a register is called “Loading”, thus the instruction to “Load” a value into the Accumulator is, “LDA”. For the X or Y register replace the “A” with “X” or “Y” resulting in “LDX” or “LDY”. Writing the value from a register into memory is called “Storing”, so the instructions are “STA”, “STX”, and “STY”. There are different methods to determine the target memory location. These methods, called addressing modes, are not equally available to all registers. The X and Y registers usually allow fewer options. Here are a few example instructions, how they acquire values, and how they do (or do not) resemble BASIC: LDA #32, loads the byte value 32 into the Accumulator. In BASIC, A=32 assigns value 32 to variable “A”. LDA 710, loads the byte value held at address 710 (decimal) into the Accumulator. In BASIC, A=PEEK(710) assigns the byte value held at address 710 to variable “A” (and converts it to the Atari's six-byte floating point format). Alternatively, imagining that memory is like a numeric array the BASIC expression A=MEMORY[710] would be conceptually similar. The prior two examples work exactly the same for the X and Y registers: e.g. LDX #32 or LDY 710. LDA $2C0,X, determines the value of $2C0 (hex) plus the value held in the X register, then loads the byte value held in that resulting address into the Accumulator. In BASIC, A=PEEK(704+X) determines the value of 704 plus the value of variable “X” and assigns the byte value held in that resulting address to variable “A”. Or, using the memory model again, this would be similar to A=MEMORY[704+X]. LDA ($D4),Y determines the address held in the Zero Page location $D4 and $D5 plus the value held in the Y register, then loads the byte value held in that resulting address into the Accumulator. Using an address to point to another address is a powerful feature of the 6502 called, “indirection” which is the basis of making reusable code that can operate on any possible memory. Change the contents of the Page Zero value, and the instruction acts on a different location. The closest parallel in BASIC: A=PEEK(V+Y) where the variable “V” defines the base location, and Y is an index added to the location. The instructions INX or INY adds 1 to the X or Y register and DEX or DEY subtracts 1 from the X or Y register. Note there is no address or option. These are one-byte instructions. In BASIC, obviously, this is X=X+1 and X=X-1, etc. In the examples above we saw the X and Y registers add an offset to a target address in different ways. This is a basis for loop control and iterating across a range of bytes held in sequential memory locations. Each of the Load instructions above has a corresponding Store instruction. These store the Accumulator value into a specified address using the same methods of determining the target address seen earlier: STA 710, STA $2C0,X. The BASIC equivalents would use POKE to store in memory: POKE 710,A, POKE 704+X,A. The third form is STA ($D4),Y and imitated in BASIC: POKE V+Y,A where V is a variable containing a base address. There is no STA #32. Storing a constant value in memory requires first loading a register with the byte value and then using a store instruction to place it into a target memory address. CMP #32, Compare the contents of the Accumulator to the byte value 32, setting flags in the CPU for evaluation by subsequent statements. A BASIC example is only vaguely similar: IF A=32 THEN ZFLAG=1. This performs the comparison and sets variable “ZFLAG” in preparation for later examination. However, the 6502 comparison evaluates several different criteria at the same time, not just this one flag. BEQ $9C40 or BNE $9C40. These cause the program execution to jump to (or branch) to the destination address based on the current state of flags set or cleared in the CPU. The CPU evaluates and sets the state of flags by a CMP instruction or any instruction that changes register contents. In this case when the Zero Flag is set due to a previous comparison then the compared values are considered equal, thus the instruction is “Branch when EQual”, BEQ. When this evaluation is true then the program branches to the target address. BNE is the opposite evaluation for when the Zero flag is not set, or “Branch when Not Equal”. The 6502 has branch instructions for each of the flags to “Branch when” the flag is set or “Branch when” the flag is clear. One special note: the branch target address must be within +/-127 bytes of the current program address. The BASIC equivalents that are roughly similar: IF ZFLAG=1 THEN GOTO 1200 or IF ZFLAG=0 THEN GOTO 1200. PLA pulls the top value off the stack and places it in the Accumulator. The stack is a hardware feature inherent to the 6502, so there is no direct parallel in BASIC. For the sake of illustration consider the stack an array and a variable called “SP” (for stack pointer) identifies the top element. A BASIC equivalent would then be: A=STACK[sP]:SP=SP-1. PHA pushes the value in the Accumulator to the top of the stack. Again, this requires an imaginary expression in BASIC: SP=SP+1:STACK[sP]=A TAX and TAY transfer the contents of the Accumulator to the X or Y register respectively. In BASIC, it is conceptually similar to X=A or Y=A. Likewise, TXA and TYA transfer the contents of the X or Y register to the Accumulator which is like A=X or A=Y in BASIC. JMP $9C40 is like GOTO 1200 in BASIC and JSR $9C40 is like GOSUB 1200 in BASIC. These instructions change the program counter to the specified 16-bit address. JSR also pushes the current program counter address on the stack allowing the subroutine to return to this location. RTS is how a subroutine exits and returns to the point where it was called. This instruction updates the program counter with a 16-bit address pulled from the stack. That address is usually pushed on the stack by a prior JSR instruction. This is similar to RETURN in BASIC. The heart of man plans his way, but the Lord establishes his steps. Proverbs 16:9
  23. Simple Assembly for Atari BASIC A multi-part discussion of a few pet peeves about Atari BASIC and simple machine language utilities to fill in the gaps. July 2016 ============================================================== Part 1 - Introduction http://atariage.com/forums/blog/576/entry-13175-part-1-of-11-simple-assembly-for-atari-basic/ Part 2 - Learn 82.7% of Assembly Language in About Three Pages http://atariage.com/forums/blog/576/entry-13176-part-2-of-11-simple-assembly-for-atari-basic/ Part 3 - The World Inside a USR() Routine http://atariage.com/forums/blog/576/entry-13177-part-3-of-11-simple-assembly-for-atari-basic/ Part 4 - Implement DPEEK() http://atariage.com/forums/blog/576/entry-13178-part-4-of-11-simple-assembly-for-atari-basic/ Part 5 - Implement DPOKE http://atariage.com/forums/blog/576/entry-13180-part-5-of-11-simple-assembly-for-atari-basic/ Part 6 - Various Bit Manipulations http://atariage.com/forums/blog/576/entry-13181-part-6-of-11-simple-assembly-for-atari-basic/ Part 7 - Convert Integer to Hex String http://atariage.com/forums/blog/576/entry-13182-part-7-of-11-simple-assembly-for-atari-basic/ Part 8 - Convert Integer to Bit String http://atariage.com/forums/blog/576/entry-13183-part-8-of-11-simple-assembly-for-atari-basic/ Part 9 - Memory Copy http://atariage.com/forums/blog/576/entry-13184-part-9-of-11-simple-assembly-for-atari-basic/ Part 10 - Binary File I/O Part 1 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13185-part-10-of-11-simple-assembly-for-atari-basic/ Part 11 - Binary File I/O Part 2 (XIO is Broken) http://atariage.com/forums/blog/576/entry-13186-part-11-simple-assembly-for-atari-basic-the-end/ ============================================================== This actually isn't the project I meant to be working on. I ended up here, because of a series of other needs. Originally, I was working on converting some old examples from Compute! not written for the Atari and needed to demonstrate a machine language sort for an Atari BASIC numeric array. This required I understand Atari BASIC variables, and while writing that discussion I realized I needed facilities not included in Atari BASIC (16-bit peek and poke, memory moves, etc.) So, that brought me here to fill in some gaps missing in Atari BASIC. Now that I'm done with this hopefully the Atari BASIC Variable article will be done soon, then I can get back to the machine language sort. it will take a while to get these articles posted, reformatted, etc. Several will be posted today, and others over the next few days. For those who don't care to wait for the multi-part bloggitty-blog version the complete document is attached below in a couple formats: LibreOffice ODT. Remove the .zip after downloading: HelpBASIC.odt.zip PDF version: HelpBASIC.pdf ============================================================== Introduction: The Good… The Atari 8-bit computer was a paradigm-changing platform when introduced in 1979. The extensive, built-in color graphics and sound capabilities started computer-based “multimedia” years before the word existed. Atari would have been justified keeping it a completely closed box of mystery. Fortunately, they did not. Atari provided a BASIC language with reasonable support for the computer’s custom hardware graphics and sound features. Many other computers introduced before and even after the Atari had BASIC languages providing little to no support for graphics or sound. They were little different from the original BASIC (circa 1964) developed in an era of time-sharing terminal printers intended for number and text processing. Atari BASIC incorporates some good ideas. On program entry Atari BASIC converts commands to an abbreviated form called tokens. Using tokens cuts down memory use for program storage, speeds up program execution, and the tokenization process provides immediate syntax error feedback at the time a programmer enters a program statement. Atari BASIC provides exceptionally high readability compared to other BASICs of the day. Atari BASIC nicely inserts spaces between commands and values in program listings. This spacing does not occupy the program's memory thanks to tokenization. Finally, Atari BASIC recognizes long variable names enhancing readability. Some do not like the way strings work in Atari BASIC, but I find they make a lot of sense. When moving on to C years later, I credit the reduced learning curve for C strings to Atari BASIC; the way an Atari string behaves in memory is more like a C array of characters (less the ‘\0’ terminator) than Microsoft BASIC strings. Also, a character pointer in C is a concept similar to the Atari BASIC ADR() function. The Bad… Of course, nothing is perfect. Atari BASIC is missing some useful features for dealing with computer hardware on the computer’s terms. This is not really a critical failure of Atari BASIC, since many other BASICs do even less than Atari BASIC and part of the purpose of original BASIC was to protect the beginner programmer from architectural questions about the computer hardware. But, given the Atari’s additional graphics and sound features part of the fun of programming is making the Atari hardware do interesting things. 8-bit computers frequently have reason to deal with 16-bit, two-byte values. While Atari BASIC has PEEK and POKE working with single-byte values it lacks a way to work directly with two-byte values. The Atari hardware provides many interesting bells and whistles for graphics and sound. (Literally, it can make bell and whistle sound effects.) Effective interaction with hardware registers and the operating system often require manipulating individual bits within a byte. Bit operations are not available in Atari BASIC. Hand-in-hand with 16-bit values and bit operations is working with hexadecimal representation of values. When one becomes familiar with the hardware it begins to make a lot more sense to refer to and display values in their hexadecimal form. Hexadecimal value representation is not included in Atari BASIC. Moving blocks of memory around has many practical uses in the Atari environment – copying character sets, animating characters or Player/Missile graphics, rapid updates of color registers, etc. Atari BASIC does not have a general purpose memory move feature. There is a common hack using Atari BASIC strings to perform a high-speed memory move. However, this method requires more setup, is not obvious to the casual programmer, and so is not as convenient and flexible as a command to move memory. Atari BASIC’s I/O functions are missing the ability to load bulk, binary data into memory from a file, (such as graphics or character sets.) Atari BASIC must use slower loops to read a file, or waste memory by including the data within the BASIC program. And the Ugly (or just darned weird)… The worst weird thing about Atari BASIC does is that it handles all numbers as 6-byte, BCD, floating point. In spite of this it is still comparatively fast, so it makes one wonder how fast Atari BASIC could be if it used 16-bit integers instead of bogging down the 1.79 Mhz CPU with floating point math. Another problem built into Atari BASIC is line lookup for statements. Atari BASIC identifies statements during execution by searching the line numbers from the beginning of the program to the desired statement. This causes statements at the end of a long program to execute slower than statements near the start of the program. Atari BASIC has two different syntax options for GOTO. These are both valid: 100 GOTO 10 200 GO TO 150 What's up with that? It is a joke, right? I do hope the implementation cost less than a dozen bytes. Couldn't this have been replaced with a solution to one of the other problems, such as 16-bit PEEK and POKE? All these issues with floating point use, line searching, and command syntax are fundamental to the internals of Atari BASIC, so solutions to these situations require writing a new version of BASIC. Since I’m not planning on making a career of this, contentment will have to come from resolving the easier problems mentioned earlier. The Solution To fix these problems get a copy of OSS BASIC XL or BASIC XE, or TurboBasic XL. Really. Seriously. These BASICs can load and run all Atari BASIC tokenized programs (that is, SAVE’d programs) and ENTER Atari BASIC LIST’ed programs correctly at least 98% of the time. Both are faster than Atari BASIC and provide many of the useful features discussed here. And with the advent of high quality emulators you don't even need to concern yourself with acquiring the languages on physical ROM cartridges or disks. You can get the whole Atari experience plus modern convenience just by downloading a few digital image files. So, problem solved. Thanks for reading. See you later. The Other Solutions You're still here? The movie is over. Go home. . . . So, trading up to a better BASIC is out of the question? For whatever questionable reason you are actually married to Atari BASIC and can’t switch to anything else? Fine. (Do not send me pictures of your children.) If there were no other options the article would end here. However, the miles of text (in the half dozen subsequent parts) must indicate interesting stuff follows. Most of the issues are less difficult than they appear. In some cases a simple solution can be written using just Atari BASIC. However, given the speed of Atari BASIC the real problem becomes how to do it fast enough to be worthwhile. The best performance comes from machine language code. Even badly written assembly will produce machine code that runs circles around Atari BASIC. Below, the article will demonstrate that it has more than enough badly written assembly code to go around for everyone. This article presents several reusable machine language utility programs expanding BASIC programs’ capabilities, and improving performance. These utilities are designed to be called from BASIC’s USR() function. This article is not entirely a lecture on learning 6502 programming from scratch. But, the solutions are not terribly complicated, so it should not be too difficult for a beginner to follow. Final solutions with the utilities implemented in Atari BASIC test programs will appear for those who don't care about the assembly code. Next time we will learn about Assembly language syntax and instructions in as few pages as possible. For I know the plans I have for you, declares the Lord, plans for welfare and not for evil, to give you a future and a hope. Jeremiah 29:11
×
×
  • Create New...