ASM programming is FASCINATING! - Page 1
Forum section: electronics / general

author: eti
author_rank: Super Contributor
timestamp: July 27, 2020, 02:54:44 am
content: Okay, so my mind works best at the lowest possible (humanly parseable) level, with regard to machines. I find ASM particular fascinating, although I do also use bash a great deal and enjoy that, but ASM and knowing how the internal machinations of digital machines work, REALLY turns on lightbulbs in my brain.I'm a lonnnnnnng way off knowing even 5% of what I need to know, but am gradually getting used to PIC ASM, and am now learning by messing around with an Altair 8800 simulator and loading registers by hand, and also referring to as much ASM as I can, no matter what the arch.I've been looking here also, at 6502:http://www.obelisk.me.uk/6502/reference.htmlIf anyone would like to comment or advise how best to get a true, firm grasp on ASM, I'm open to suggestions.Thanks guys and girls.

author: Brumby
author_rank: Unknown
timestamp: July 27, 2020, 03:19:26 am
content: First, I had to get a handle on what ASM programming was. I took a guess and found I was right - but TLA's have come far further along than seems necessary (IMO).My first foray into programming was writing in IBM assember and one of my favourite books was the S/360 Principles of Operations manual. I did 6 years writing applications software in assembler for one company and then changed jobs to another for my assembler skills.Yes, it is good fun when you pick up a dump, locate the registers then start reading the instructions directly off the hex. Debugging can be fun, though, especially when the module you're checking is 7MB ... on a 15x11 printout a couple of inches thick.My recommendation is to start out finding a development platform (which I'll leave others to suggest) and then try a simple example program and/or get a program that actually does something and change some things to see if you can make it do something different, that is according to your intention.You will find, however, that there are many routines that you will use over and over, so re-writing them every time will wear you out very quickly. Having blocks of code you drop in can ease this, but mostly there will be precompiled modules you will just call (DLLs for example).Also, depending on your development environment, the ability for you to create garbage code is greatly increased. Having a strict discipline in how you do things will become extremely important. One example is spaghetti code - all too easy to do in assembler, but great for getting yourself into trouble if you're not careful. Learn to do structured programming. It will save you when programs get complex.

author: MK14
author_rank: Super Contributor
timestamp: July 27, 2020, 03:25:42 am
content: Turning your question upside down, and inside out.What exactly do you want your Assembly Language programs (ASM), to do exactly ?Perhaps in the future, when you know how to do ASM programming well.Answering that, will lead to potential recommendations of the best processor(s) to go for and/or emulate/simulate.Then you can go typing away, creating/learning your assembly language, and run it.Unlike high level languages, it is less easy to move between architectures, so the sooner you know which processor you want to spend significant amounts of time with, the better.But you can move to different architectures, reasonably easily, but any code you have already written, will no longer work, without fairly major, conversion/rewriting.Unless, you just want to learn tiny bits of lots of different architectures, and never actual write any significantly long, working programs as such.Your main choices would be 8 bit. Relatively easy, straight forward, and suitable for small/simple/easy programs, to do simple tasks. Such as counting from 1 to 100, on a seven segment display.16/32 bit, such as the Motorola 68000 series (relatively obsolete these days, but practicable to program by hand, unlike many later processors, which can be done, but are considerably harder). Better suited to bigger, more complicated programs (although 8 bit could in theory do just about anything), a fair bit faster (often), potentially easier for more complicated tasks.Desktop PC x86, hardware floating point available, can do very big and complicated programs, you probably already have access to these.At a guess, maybe Arduino is a good choice. You can get them (clones), relatively cheaply (e.g. £1.50), program them from a PC using USB and freely available IDE's. They are 8 bit (other versions are available as well), tons of information about them on the internet, and they have a fairly straight forward, 8 bit architecture.There are different versions, which I won't list here, such as the Arduino Uno.They can easily interface to hardware (pre-built I/O stuff, is cheaply and readily available, such as displays, keyswitch boards, etc), and have a similar instruction set (8 bit), to some of the older generation, 8 bit processors.You can even mix C code with some ASM, and/or use pre-existing libraries.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: July 27, 2020, 04:03:51 am
content: I've gone all the way around the circle, I think; early on I started with QBASIC (because it was accessible, and easy), and some rudimentary x86 ASM (because MS-DOS has DEBUG, and I felt a cryptic mystique around using it). Later, I moved on to Java and C, but also learned a few other instruction sets (AVR and Z80). Most recently, I've finally done a complete project, in GCC, incorporating one ASM module (for AVR). So I've learned GAS' particular syntax, avr-gcc's ABI, etc.I don't recommend x86 for starting out, but it's certainly accessible, and available... ARM too, especially these days. Learning on a classic platform, like recommended above, is probably a good idea. Take some months to learn it, write some algorithms; take a break, come back and do some more. Learn a new instruction set, and so on. Repeat until you're comfortable with how most traditional ISAs work.I... well, what I'd have to say is, I'dliketo be able to write C in terms of, these statements compile to those instructions, and so on. But there are too many layers of abstraction, with too ineffective optimization, for that to be the case, at least on the last (AVR) project. This is an effective way to approach HDL I think, i.e. that certain combinations of statements (processes) produce certain kinds of registers, gates and so on. But a compiler like GCC at least doesn't quite allow that sort of writing. But I can say, I'm fluent in both formats, and would gladly write that way if I could.(The danger of that type of writing, is the high level code starts to take on structures that bubble up from the underlying hardware; when porting to different hardware, it may require structural changes to the code itself, if not semantically -- as long as the C code is valid, not abusing edge cases, built-ins and such, it should compile correctly on any platform -- but for best optimization results, this can be a concern.)Heh... I guess what I'm really trying to say is, not so much anything about an ISA, just a whine about how GCC 9 is so bad on AVR, I can't "write for the platform" even if I try.The upside to that is -- since I'm not even able to write for the platform (in a usefully optimized way), the code is forced to remain general and should compile, if notwell, then at leastcromulently, for other platforms.But anyway -- even if you can't "write for the platform" (and, really, you shouldn'tstrivefor it, exactly because of portability), you can always review the assembled output from the compiler, and see how it's turning statements into instructions, and see which expressions and statements have more overhead than others, etc. It's a good exercise, and being comfortable with the platform's ISA makes debugging so much easier.Tim

author: peter-h
author_rank: Super Contributor
timestamp: July 27, 2020, 01:15:53 pm
content: The original Z80 etc CPUs were programmed almost entirely in assembler, partly because memory was short and partly because compilers generated horrible code.I wrote megabytesof code for these chips in the 1980s and 1990s.You need to be pretty good - much better than today's "PHP hackers" - and disciplined. But your general productivity won't be anywhere near as good as that of a good C programmer doing the same job.

author: BravoV
author_rank: Super Contributor
timestamp: July 27, 2020, 01:46:51 pm
content: Its a lost art imho, in the old days was called ->Demosceneor called 4K code challenge, another example ->This 4-kilobyte demo squeezes a universe of fractals into the size of a Word documentThis scene and the music (like crap I know), is generated by just 1K bytes code

author: Benta
author_rank: Super Contributor
timestamp: July 27, 2020, 06:00:58 pm
content: Still lots of assembler code being written today, but mainly for controlling issues very close to the hardware.Typically CPU/peripherals initialization, boot loaders, exception handlers and of course device drivers (at least at the lowest level).The most developed and mature architecture for assembler programming is imho the 68K, but today it's a dead end. Probably ARM is the way to go today. But now we're moving back to the "architecture wars" of the 1980/90s.

author: nctnico
author_rank: Super Contributor
timestamp: July 27, 2020, 07:44:24 pm
content: I recommend not to start with MIPS; this has a 1 delay slot in the pipeline which executes the instruction after a jump instruction. Other than that each architecture has its pros and cons.

author: NivagSwerdna
author_rank: Super Contributor
timestamp: July 27, 2020, 07:50:33 pm
content: http://www.retrotechnology.com/memship/memship.htmlWhen you get to the bit level (the elf is loaded using the switches) then you will get a good understanding!ASM to too high level... you need to know how the bits map to the opcodes.PS1802 has a nice opcode layout

author: Benta
author_rank: Super Contributor
timestamp: July 27, 2020, 07:57:58 pm
content: Quote from: NivagSwerdna on July 27, 2020, 07:50:33 pmNah! If you want to go really deep, the Motorola MC14500 is the thing.http://www.ganssle.com/articles/quirkychips.html

author: Benta
author_rank: Super Contributor
timestamp: July 27, 2020, 08:01:19 pm
content: Quote from: nctnico on July 27, 2020, 07:44:24 pmTo paraphrase Frank Zappa: "MIPS is not dead, it just smells funny."

author: KL27x
author_rank: Super Contributor
timestamp: July 27, 2020, 08:30:41 pm
content: If you want some guidance on basic practices, gotchas, and the finer points of MPLAB IDE as pertains to aiding in assembly programming, you might be interested in David Gooligum's PIC assembly tutorials.

author: NivagSwerdna
author_rank: Super Contributor
timestamp: July 28, 2020, 10:55:07 am
content: I should have mentioned... there is a really nice set of videos on YT from Robert Paz (Sadly RIP now)https://www.youtube.com/playlist?list=PLifLftIJFUm-gv-OQr_7WbsKdn3i6zdZDThese, although the IDE part might be slightly out of date, show Assembly on the Arduino Uno / AVR.... so if you want to explore that architecture it might be interesting.It all depends... having breakpoints is always nice... that might affect your choice of platform.PSI haven't commercially written any assembler for decades... C compilers are too good!

author: Kleinstein
author_rank: Super Contributor
timestamp: July 28, 2020, 12:09:40 pm
content: ASM programming is still sometimes done with µCs. I does offer the advantage of a well defined run time. So those old days style waiting loops get accurate.The AVRs can be a good starting point. The AVR studio dev. environment has a relatively nice simulator. So one can do the first tries even without real hardware. It also helps with debugging.

author: MK14
author_rank: Super Contributor
timestamp: July 28, 2020, 12:32:28 pm
content: Quote from: Kleinstein on July 28, 2020, 12:09:40 pmWith the rather old processors/architectures, your are right. The odd modern one, here and there can be an exception as well (e.g. XCore).But, unfortunately, with processors beyond a certain level of complexity (superscaler, data/instruction caches, branch Prediction Speedup Things, some pipelines (especially long ones),https://en.wikipedia.org/wiki/Out-of-order_execution, etc), they become no longer fully/really predictable, as regards 100% precise timing, of small program structures. Even if it is written in pure assembly language.E.g. It *might* be in the cache and/or branch speed up mechanism and/or be able to do multiple-instructions this machine cycle (depending on various factors, including clashing register usage etc).Plus complications because of pipelines, I.e. it is almost instantaneous, because the pipeline allows it, or the pipeline has to be flushed, etc etc.Disclaimer: The above paragraphs were written quickly and may have small (or bigger) technical inaccuracies. If you are really interested in this stuff, there is some good/interesting stuff (books and good internet stuff) out there.

author: tggzzz
author_rank: Super Contributor
timestamp: July 28, 2020, 02:53:52 pm
content: Quote from: Kleinstein on July 28, 2020, 12:09:40 pmOnly in very simple processors that don't have any cache, don't have interrupts, and are strictly in-order.Even then it is difficult to guarantee timing in all but the simplest systems or the most lightly loaded systems. If you doubt that, specify which scheduling algorithm you are using, and find a way toconvince other people that your implementation meets the requirements. That is surprisingly difficult.There is, as MK14 notes, one modern alternative that guarantees hard realtime performance by design: the xCORE processors running xC. They use parallism to avoid the need for interrupts, use hyperthreading techniques to avoid the need for cache, have FPGA-like i/o structures, and the IDE inspects the (optimised) code to determine the exact timing betweenhereandthere. No other processor comes close.

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 28, 2020, 03:07:31 pm
content: Quote from: tggzzz on July 28, 2020, 02:53:52 pmYep.The "last" processors I did that on - already over 15 years ago now - were PIC16F/18F CPUs. Cycles per instruction were fully documented and fixed for all instructions, and you could indeed predict execution time with 1 cycle accuracy. If you needed *short* and accurate delays, like just a few cycles, that was pretty much the only way.I did that with even older processors, such as the Z80 - with interrupts disabled.Quote from: tggzzz on July 28, 2020, 02:53:52 pmYeah. Definitely a completely different architecture.Getting back to the topic, programming in assembly these days on modern processors can be certainly fun and somewhat fascinating, you can indeed get more performance this way - if you're very good, otherwise most compilers will beat you to it - but don't expect to be able to accurately predict execution time. Or stick to the very simple processors. As I mentioned, that's something you can still do with PIC16F/18F MCUs for instance, which are still available.As to getting a "good grasp" on ASM? I would probably start as we used to back in the days - first study the architecture of common (and simple) processors, get to know everything they are able to execute (arithmetic ops, logic ops, branches, memory move, etc.) and then learn the corresponding assembly. At this point, it'll be easier to understand what every assembly intruction does and WHY it has been implemented. Then of course, look at a lot of code examples, and write your own. If you're interested in the 6502, I think you'll find thousands of resources for that.

author: NivagSwerdna
author_rank: Super Contributor
timestamp: July 28, 2020, 05:33:16 pm
content: Of course there is also the joy of reverse engineering using Ida Pro or Ghidra or the like.If you are interested in computer security etc... then Buffer Overflow attacks are fun and there are plenty of tutorials out there..e.g.If that's your thing then Linux, gcc and gdb are your friends.Have fun!

author: MK14
author_rank: Super Contributor
timestamp: July 28, 2020, 06:57:15 pm
content: Quote from: MK14 on July 28, 2020, 12:32:28 pmNo one else seems to have so far, so I will 'attack' my own post.Actually, no, even the old processors, were not necessarily, cycle by cycle accurate.Examples:WAIT state lines (might be called something else, depends on cpu, e.g. ready), which inserts wait states, to help with slower memory, I/O and sometimes other things. Some schemes are somewhat non-deterministic (e.g. a video card, which may or may not, be accessing the video memory, at the same instant in time).If the looping software is waiting for a bit or value, in an internal or external peripheral, to occur or change, the timing can be asynchronous and/or random. Because (e.g. the A to D converter), the precise timing may depend on how long the analogue and/or digital circuitry, takes (potentially asynchronous to the master cpu clock).Some instructions, have a variable number of cycles, depending on the exact values, and don't seem to bother to define what they are. E.g. Some integer divide instructions. Might say, takes from 2 to 15 cycles, depending on values involved. It could be deterministic, if you researched how the cycle count varies, but most people probably won't do that. There could be documentation somewhere that defines it (e.g. each 1 in specified register, adds a cycle to the time plus a minimum of 2 cycles plus address mode penalty cycle count).Not an exhaustive list. Also not the best of examples, because they are sort of deterministic, it is just the exact times in practice will vary, depending on somewhat defined things (such as A to D response time variation) etc.

author: chris_leyson
author_rank: Super Contributor
timestamp: July 28, 2020, 07:26:02 pm
content: I think the most fun I've ever had writing assembler was for Motorola DSP chips. You always had to keep an eye on how your data was moving, which busses or registers were free to move the data and then sometimes tweak the order of operations so as not to get an arithmetic, transfer or status stall whereupon the assembler would add a NOP instruction. I was good fun interleaving mathematical operations and data moves in order to keep the ALU as busy as possible. NXP still do the 56k family.

author: tggzzz
author_rank: Super Contributor
timestamp: July 28, 2020, 08:22:29 pm
content: Quote from: MK14 on July 28, 2020, 06:57:15 pmAll true, but most are "outside" the computer, and so "don't count". The "difficult" cases involve instructions like that division instruction, and non-fixed loops in general.

author: MK14
author_rank: Super Contributor
timestamp: July 28, 2020, 09:01:19 pm
content: Quote from: tggzzz on July 28, 2020, 08:22:29 pmI agree.Assuming no wait states (fixed duration defined time ones, can be calculated as extra cycles), interrupts and anything else that can affect the timings, is active. The cycle times (on certain cpus, only, e.g. old ones), is predictable/deterministic.My bringing in less deterministic, external (sometimes internal) events, is wrong. Until you start worrying about the overall system, in a complex real time system, which is not what we are discussing.

author: tggzzz
author_rank: Super Contributor
timestamp: July 28, 2020, 09:23:10 pm
content: Quote from: MK14 on July 28, 2020, 09:01:19 pmIt isn't "wrong" per se, and is important, but it is arguably a different discussion.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: July 28, 2020, 09:24:18 pm
content: The lesson on timing is, of course: if you have enough CPU power to do it, and deterministic timings, then you can hard code it; if not, then buffer it, and make sure you have enough CPU power to get through the worst-case paths to refreshing that buffer in time. AVR (most of them) don't have DMA, but most ARMs do (or above the entry level Cortex M0 tier, say). Those ARMs usually also have caches -- though they may be documented cryptically, e.g. a "Flash memory accelerator"...This is what PCs do; although, that PCs can deliver multimedia at all, is still something of a miracle on top of that. Most OSs don't guarantee program execution within, really, any period of time. The CPU is one thing, but the OS is a huge pile of APIs, caches and priority queues, and the granularity is not very impressive. They just happen to work most of the time, say, jumping back into your program every millisecond or so.Indeed, modern application processors are so fast that you might not care at all, about their indeterministic execution time; an AVR or Cortex-M0 can execute one or two instructions, in the time it takes the big CPU to execute a hundred -- and those instructions are vastly more powerful, operating on more data (including SIMD extensions) in ever richer ways. In that fraction of a microsecond, the entire computation might be complete, whereas the deterministic CPUs are just sitting down to work. Not to mention if multiple cores are employed (not that their outputs will be combined until much later, due to inter-CPU communication and cache coherency). This is partly why a, if imperfectly (but you need to use a kernel mode driver to get around the OS's context switching).Tim

author: MK14
author_rank: Super Contributor
timestamp: July 28, 2020, 11:41:41 pm
content: Quote from: T3sl4co1l on July 28, 2020, 09:24:18 pmIn practice, there are various techniques, for mostly getting back, near (enough) deterministic capabilities. From powerful modern cpus, such as the latest PCs. You described one way.For example, in game programming. It is usual to use the fact that you can easily get the actual exact elapsed time, and use that, when you are calculating time dependent things.So, although the actual time jitters about, like crazy. The calculations, move things in proportion, to how much time has elapsed, since the last event/redraw/movement, of the game object that you are currently processing.I began to be impressed with your video clip, putting Doom, onto relatively ancient, very low capability hardware. Until I heard them say about putting a Raspberry PI in it. Not to be confused with, totally and utterly cheating, I guess. It is still a significant challenge. To interface it, into the cartridge, and get the low resolution, limited number of colours. To act, like a much more modern, somewhat high resolution, many different colour, display.So all things considered, it wasn't too bad!

author: joeqsmith
author_rank: Super Contributor
timestamp: July 29, 2020, 12:40:34 am
content: Quote from: eti on July 27, 2020, 02:54:44 amDon't.Each instruction set is different. The hardware will be different as well. The more of them you work with, the worse it gets. You start making up instructions that are not there or on some other micro. You spend hours trying to come up with better techniques, and for what? Then you have to work on a RISC andWe used to do everything it seems in assembler. I had friend who worked at a fairly large company and he was in the third tier optimizing group. The first level was the general coders. Their stuff was clean and easy to follow and maintain. If the code wouldn't fit, it went to the second group who would try and trim it up but keep a similar flow. The third group would rewrite whole sections to try and get every last byte out. The code would be nothing like the original and normally be difficult to understand.I don't miss those days, but when I built my little transient generator, I wire wrapped the controller using a 6801 and wrote it all in assembler. Just for the fun of it. That codes a mess and the whole project I would say took at least 10X longer.I've written a few disasseblers and debuggers for smaller micros. I've also designed and built my own ICE. At one point I was rolling my own CPUs. Similar to the ucode on the 68332, create your own micro, then you can create your own uassember. Fun, but today, there would be little point in any of it.

author: tggzzz
author_rank: Super Contributor
timestamp: July 29, 2020, 01:03:33 am
content: Quote from: T3sl4co1l on July 28, 2020, 09:24:18 pmMaking sure of that is very difficult. If you measure a mean time of X, what fudge factor should you apply to get to the worst case?As for "if you have enough cpu power", yes that is a solution. But I'm reminded that you can get a brick to fly if you apply enough power. (Or search yootoob for "flying lawnmower"!)QuoteOr might not be complete.I'm reminded of the old joke about someone entering a programming competition. The winning entry was faster, but contained errors. The losing competitor remarked that he could have made his program ten times faster if he didn't have to give the correct result.QuoteCache coherency is a killer, both in large systems or hard realtime systems.The larger HPC systems appear to be settling on message passing architectures, which can avoid the problems of cache coherency.

author: MK14
author_rank: Super Contributor
timestamp: July 29, 2020, 01:51:30 am
content: Quote from: joeqsmith on July 29, 2020, 12:40:34 amBut, doing at least some assembly language. Can be fun and educational. Even a few hours at it, can be rather educational and fun. Even if that is the first and last, short assembly language program you ever end up writing.

author: VK3DRB
author_rank: Super Contributor
timestamp: July 29, 2020, 01:54:01 am
content: No one has ever written anything in assembler.But they have used an assembler and written code in assembly language. There is a big difference between an assembler and assembly language. Notepad++ now call it "Assembly" because I advised the authors several years ago they had the terminology wrong. They agreed. (Notepad++ --> Language --> A --> Assembly).These days I write in C, but for register settings and digital I/O there is little difference between C and Assembly. I have not bothered with Assembly language for years except for some inline code occasionally. But I can see why you like Assembly language. For larger programs where there is complex program flow, C is much easier to use, read and understand.That being said, one of the most important things in writing in assembly language (or C) is commenting. Don't comment WHAT you have done on a line-by line basis as it is pointless, but comment WHY you have done things. Use intelligent labels. Get the commenting right and use a format that ensures no ambiguity between comments or blocks of comments. Use decent good headings too. Comments will help you when you revisit the code a few years down the track. Also, it helps the "next poor bunny" that has to pick up the code. There is nothing worse than un-commented assembly code. I have seen some "professional" code written by well known companies with very amateurish commenting. Commenting in C can be reduced if intelligent identifier names and function names are used, rather than nonsensical or ambiguous names that only confuse.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: July 29, 2020, 05:33:14 am
content: Quote from: tggzzz on July 29, 2020, 01:03:33 amIs this an argument in favor of or opposition to my post?I like it, it's actually a really good analogy. It highlights the same gross excess, and rational economy.Back in the day, it took crazy defense projects (or a few very dedicated and probably wealthy amateurs) to come up with junk like that (e.g., those ill fated flying-saucer platforms). Nowadays anyone with under a thousand bucks knocking around can slap together something like that!In the same way, what used to require heroic assembler on one platform, with today's platforms is now trivial, even on a budget. Don't let some imagined combination of efficiency, elegance and so on, be the barrier to "good enough"!Doing some boring housekeeping tasks? Don't worry about learning 8051 assembler, just slap in the AVR or STM32 you're familiar with. Cost reduce it later when you have time -- and more importantly, budget -- to!Using 10s technology to force 90s games to "run" on an 80s console? Don't worry about learning VHDL for the bus interface, just use the rPi you're handy with!Fluent in Python but the data-cranking problem would really do better on a DSP or FPGA? Toss in the $50 SBC, who cares!And of course not that one should take such liberty for granted: there will always be some applications where the harder solution is required, so there is value in learning lower level things (even assembly). By all means, take the time to investigate them, as you can.QuoteYup. Timing from start of instruction(s) is what I meant, of course; but knowing when they start, is another matter (or when their outputs propagate to their targets).QuoteReminds me of this story:https://randomascii.wordpress.com/2018/01/07/finding-a-cpu-design-bug-in-the-xbox-360/tl;dr they added an instruction to perform an incoherent prefetch, bypassing L2. Turns out... just putting that instruction into memoryanywhere executable at all,* introduced an extremely small probability that it would be speculatively executed, tainting coherency and setting up a crash with absolutely no warning or explanation.*Hmm, doesn't say if it was tested quitethisfar. A branch-indirect instruction could potentially be predicted to land on one, even if the target is not in any intended executable code path. (Also assuming memory is r/w/x tagged, so that general data doesn't get spec-ex.'d; that would just about damn it to a respin, I would guess!) Depends when and how such an instruction is decoded; maybe those are really slow on the platform, decoded late, and it's actually safe?Tim

author: KL27x
author_rank: Super Contributor
timestamp: July 29, 2020, 06:56:41 am
content: To the OP, here's a thread that touches on many specifics of PIC ASM. Some gotcha's, some IDE tricks, some general considerations for actually writing assembly for 8 bit PIC microcontrollers. It's easy reading and might provide some insight.https://www.eevblog.com/forum/beginners/assembly-code-help!-pic16f57/msg1857261/#msg1857261BTW, I'm pretty sure I basically fixed the bug. But the last code change I made, like any good scientist I changed two things at once. If the guy just undoes one of those things, I bet the bug is fixed. In my defense, I had real, important work on a short timeline fall in my lap at that time. And I also completely lose interest in the final details once I know I can do it. If he had gifted me a machine, now that would have been different.

author: tggzzz
author_rank: Super Contributor
timestamp: July 29, 2020, 07:57:48 am
content: Quote from: VK3DRB on July 29, 2020, 01:54:01 amI'm not entirely sure what you mean, but what do you think I wrote in for my first computer? The total memory available to me in my house and at college was 128bytes.Yes, bytes.

author: tggzzz
author_rank: Super Contributor
timestamp: July 29, 2020, 07:59:58 am
content: Quote from: MK14 on July 29, 2020, 01:51:30 amBeing able toreadassembly isextremelyuseful when doing embedded programming, and having to work out what the compiler+hardware is actually doing!Ditto optimising the shit out of inner loops.Ditto assuring yourself that the multicore code has the necessary memory barriers.Plus I've used it as the basis for interview questions along the lines of "outline in pseudocode the instructions generated for a C function call".

author: KL27x
author_rank: Super Contributor
timestamp: July 29, 2020, 08:06:26 am
content: Just semantics. An assembler is analagous to a compiler. You wrote programs in assembly. The assembler turned that into machine op codes. Even if you wrote in machine code, you still didn't write in assembler/compiler.

author: MK14
author_rank: Super Contributor
timestamp: July 29, 2020, 08:14:37 am
content: Quote from: KL27x on July 29, 2020, 08:06:26 amAs well as calling it, "I just wrote an assembly program", people also in practice seem to say "I wrote it in assembler".But people never seem to say "I wrote it in compiler".Similarly, paper, when printed, is then called a newspaper.But you might say to someone, (when you want the newspaper) "please hand me the paper", or "Please go and fetch the paper".

author: westfw
author_rank: Super Contributor
timestamp: July 29, 2020, 08:15:15 am
content: QuoteI would strongly suggest investing time in a modern processor, rather than all those historical beasts.MIPS (ie PIC32), ARM, or RISC-V...(the problem is, you won't find nearly as much tutorial material on how to program them in assembly language.)

author: tggzzz
author_rank: Super Contributor
timestamp: July 29, 2020, 08:16:04 am
content: Quote from: T3sl4co1l on July 29, 2020, 05:33:14 amYesQuoteNo argument there, but what underutilisation fudge factor should you employ toguaranteeto meet timing constraints.My professional experience includes bothhardrealtime embedded systems (where "deadline" was the right term), and soft realtime systems where the mean latency was important. The former is "challenging" if you have caches around - it is best to disable them.QuoteI haven't heard that story, but it doesn't surprise me. I've deliberately avoided looking at the data sheetand erratafor modern x86 processors. As Tony Hoare put it in his Turing award lecture, "There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult. It demands the same skill, devotion, insight, and even inspiration as the discovery of the simple physical laws which underlie the complex phenomena of nature."Having said that, the xCORE processor I used had zero errata and the processor's hardware-software co-design made it a delight to use. Programming it was fun, not a battle.

author: KL27x
author_rank: Super Contributor
timestamp: July 29, 2020, 09:08:06 am
content: QuoteYeah, and it doesn't bother me, at all. People also "compile" their assembly into a hex.edit: I have yet to hear anyone recompile their assembler. That might bug me.

author: MK14
author_rank: Super Contributor
timestamp: July 29, 2020, 09:11:09 am
content: Quote from: KL27x on July 29, 2020, 09:08:06 amWhich they thenuploaddownloadinto the cpu.

author: Psi
author_rank: Super Contributor
timestamp: July 29, 2020, 09:13:28 am
content: Yep, ASM is definitely fascinating.I wouldn't want to write a massive windows app in it, but it definitely gives you a good appreciation for how things work behind the scenes.I took a class in computer architecture and then advanced computer architecture at uni in my computing degree, it was very worthwhile.Then after that degree i got another one in electronics with emphasis on mcu programming. hehe

author: Ian.M
author_rank: Super Contributor
timestamp: July 29, 2020, 09:39:30 am
content: Quote from: KL27x on July 29, 2020, 06:56:41 am@KT27x: Fixed the link for you.@all,I was an interested bystander and limited participant in that thread. IIRC It was an exercise in patching the worst spaghetti code that has been seen here in many years on a deeply unfavourable architecture.IMHO Assembly language programming of complex projects on the baseline (12 bit) PIC core risks brain damage to all participating sapient lifeforms vulnerable to impact trauma, and not capable of directed autistic savant hyperfocus.For your own safety, please use adequate PPE (e.g. boxing helmet and thick padding on desk and cubicle walls), and have your significant other on standby with a tranquilliser, straitjacket and your preferred choice of sedatives.If you engage in rubber duck debugging, you will find that the ducks are a consumable resource!The classic and extended midrange (14 bit) PIC cores are much less trauma inducing, and can actually be satisfying to code for (in a hair-shirt ascetic sort of way).TLDR: If the second group of numbers in a PIC12 or PIC16 part number begins with a '5', run screaming for the hills!

author: ebastler
author_rank: Super Contributor
timestamp: July 29, 2020, 10:42:29 am
content: Quote from: KL27x on July 29, 2020, 09:08:06 amYep, me too.On a loosely related note: Is there a commonly accepted term for the process of translating VHDL or Verilog into an FPGA configuration binary? "I synthesized, mapped, placed & routed it" is a bit long.

author: Ian.M
author_rank: Super Contributor
timestamp: July 29, 2020, 10:46:36 am
content: "I built the binary from the ....." or is that over-simplistic?

author: mfro
author_rank: Regular Contributor
timestamp: July 29, 2020, 10:53:53 am
content: Quote from: ebastler on July 29, 2020, 10:42:29 amRecompiling my assembler regularly - with every binutils upgrade.

author: joeqsmith
author_rank: Super Contributor
timestamp: July 29, 2020, 12:09:34 pm
content: I took a class where we used an old computer using machine code. You found yourself memorizing the hex codes for the instructions. There was no debugger so you would enter your code and just see if it worked. There was no way to store your program and you had to key it in everytime you worked on it. The first time I used an assember was after taking that class. I'm not sure if the punch cards were any better....

author: VK3DRB
author_rank: Super Contributor
timestamp: July 29, 2020, 12:13:55 pm
content: Quote from: tggzzz on July 29, 2020, 07:57:48 amYou wrote it in Assembly language. An Assembler is a type of compiler which produces object code from source code. It is a tool, not a language. It is a common mistake that people call Assembly language Assembler.128 bytes is pretty small. Some really brilliant coders were Dan and Kathe Spracklen who write Sargon for the TRS-80. They did a brilliant job with the graphical Chess game for which I was never able to beat the machine after level 4 of 8 levels. So little memory they had to deal with - 4kB (that's 4096 BYTES) if I recall. They deserved a Nobel Prize for what they achieved.The lowest level I got was writing in hexadecimal machine code for a 6502 in an Apple II clone to make a Morse Code trainer. It worked a treat. There was no way its BASIC interpreter could generate deterministic dits, dahs and pauses at different speeds. Assembly would have worked, but I did not have an assembler handy. Much more recently about 10 years ago I developed an interractive Morse Code beacon - written entirely in C on an ATmega128 (with some audio phonemes and phrases added by a radio announcer into a speech recording chip), which has been transmitting FM on 145.650MHz for the past 10 year continuously across the city of Melbourne. Accurate timings for the Morse up to ridiculously high speeds, but I limited it from 2WPM to 30WPM (because the spare RAM would run out because it stores the previous few minutes of Morse Code in bit patterns for subsequent reading back in speech).It is fun working with such fundamental microcontrollers. I am working with some Linux developers on an ARM7 based MCU. They cannot do a lot of things that are done easily with low level MCUs. They need to find a driver that someone has written. This causes a major headache because it has limited the peripheral choices I have had to design into a PCB. Now they say they cannot even guarantee a 32.768kHz clock output frequency to within 500ppm to drive a WiFi/BT module's sleep mode, so I had to use a hardware oscillator to do it.

author: tggzzz
author_rank: Super Contributor
timestamp: July 29, 2020, 12:51:29 pm
content: Quote from: VK3DRB on July 29, 2020, 12:13:55 pmThen you wonder how to implement a mid-level processor such as a PDP11. You notice all the rx<-rx+1 and pc<-pc-123 and rx<-+[rz++] type operations, and think "hmm, each of those looks like several steps in a programming language".And at that stage you have triumphantly re-invented microprogramming. Then you notice the AMD2900 family, and go "ah ha!".QuoteYes indeed, the trick is to find the right boundaries between hardware and various levels of software.The nice thing about 486 MSDOS computers was that once MSDOS had loaded your program (and no others), you could completely ignore it.

author: mfro
author_rank: Regular Contributor
timestamp: July 29, 2020, 01:46:04 pm
content: Quote from: VK3DRB on July 29, 2020, 12:13:55 pmDid you ever think you might be talking to the wrong people?It appears to me as if what you are really searching for is Linuxdriversdevelopers.These are the guys that are talking to the hardware directly and probably can provide you with the features you are expecting.

author: peter-h
author_rank: Super Contributor
timestamp: July 29, 2020, 03:14:17 pm
content: Today, the main use of asm programming is- CPU and peripheral register initialisation which needs to be exactly right and often needs to be loaded in a particular order e.g. some registers are not accessible until after another has been suitably configured. Most people do this in C too, but they would be mostly stuck if they could not copy startup code ripped off from the dev kit source examples- Squeezing last drop of speed in some tight algorithm. However, to really make this shine you need to take a rather lateral look at the job and make use of side effects. For example most (all?) C compilers will implement a 16 bit integer x10 multiply with, ahem, a 16x16=32 multiply routine, or by calling a MULT instruction if there is one (not on a Z80, but a Z180 has one), but a shift left, save, shift left twice more, add, gives you x10 and is probably the fastest of all unless the CPU has a barrel shifter which you may get on a $8 ARM with 10M transistors, but not on a $1 chip... I have seen C compilers which try to identify these special cases but they can't do them all; say x9? Or x11? And most C compilers are truly horrible with floats; e.g. one IAR Z80 one implemented a sscanf() with a float mult for each digit converted (taking tens of milliseconds!!!!!) but not only is this garbage but if e.g. you know the multiplier is just an 8 bit value you can shortcut it by a hack on the mantissa and an increment on the exponent, which can be 1000x faster... etc. On the $8 ARM 32F I would probably not bother but it all depends on how far you are pushing it in your project. And somebody will point out that the above x10 can be done in C also, with shifts etc.- Self modifying code. This is pretty dirty but a great technique for some very fast stuff. Normally one doesn't modify opcodes but modifies the data fields in an instruction. It can speed up code several times.- Timing loops. There are cases where you need a precise delay, and you can afford to disable interrupts. A C compiler can do that too, but its next upgrade might optimise that loop out, and it will happen 5 years after the original programmer has left...- A simple development environment. I have a few Atmel 90S1200 (obsolete now) products going and the whole devt environment is a DOS directory with the assembler, the source file, and a batch file for running the assemblerModern graphical devt environments are massive things, with big learning curves, and it will take you days to get back into it after a few years. Or it may not even run if the machine got upgraded from winXP to win10, etc. So futureproofing gets hard. But the old asm kit runs under a winXP VM for ever. Actually you get the same issues with FPGAs which is why I dumped any involvement in them many years ago (Xilinx); the tools and the learning curves, and copy protection, just got more and more horrible.- An easy learning curve. It is really easy to get into asm programming. And really easy to pick up a project after 10 years to do a small mod to it (use Notepad).

author: Brumby
author_rank: Unknown
timestamp: July 30, 2020, 01:52:34 am
content: If you are into swinging on monkey bars with 11KV 3 phase on adjacent rungs, then you'll enjoy this...Quote from: peter-h on July 29, 2020, 03:14:17 pmDoing this requiresgreatcare. If you have any conceivable element of luck in your approach or risk assessment - don't. It WILL blow up on you.My advice is - DO NOT do this. Look for a more conventional approach - BUT if you absolutelyhaveto resort to something like this - DOCUMENT THE HELL OUT OF IT!!

author: mfro
author_rank: Regular Contributor
timestamp: July 30, 2020, 06:07:36 am
content: Quote from: Brumby on July 30, 2020, 01:52:34 amIt will not even work (out even) for basically all modern CPUs. A dirty trick of the past.

author: BravoV
author_rank: Super Contributor
timestamp: July 30, 2020, 06:08:14 am
content: Quote from: Brumby on July 30, 2020, 01:52:34 amIts the bread & butter for malware I guess.

author: tggzzz
author_rank: Super Contributor
timestamp: July 30, 2020, 06:19:22 am
content: Quote from: mfro on July 30, 2020, 06:07:36 amIn that case, how do JIT optimisers for modern language runtimes work? They create code fragments on the fly at runtime, then execute them.You do, of course have to ensure that there are the necessary memory barrier instructions in the JITter.

author: mfro
author_rank: Regular Contributor
timestamp: July 30, 2020, 06:32:46 am
content: Quote from: tggzzz on July 30, 2020, 06:19:22 amThey either have to do (ineffective) cache flushes or use other means to ensure that the icache doesn't contain old code (like executing a sufficient amount of other code first).

author: Berni
author_rank: Super Contributor
timestamp: July 30, 2020, 06:33:02 am
content: Quote from: mfro on July 30, 2020, 06:07:36 amIt does work on modern CPUs.It indeed does not make sense to use it for the things that self modifying code was used on 8 bit computers of speeding up things. Doing that on a modern CPU will in most cases just confuse the heck out of its long pipeline.But JIT interpreters found a new use for this. The program looks at its uncompiled (ie. JavaScipt) or bytecode compiled code(ie .Net) and translates that into machine code instructions, then jumps into those machine instructions and executes them until reaching a part that has not yet been compiled where a jump back into the JIT interpreter is placed, making it compile up some more machine code and jump back in to continue running. This makes for a massive speed boost compared to the oldschool emulation based interpreters.Another use for self modifying code on modern PCs is game console emulators. Sometimes PCs are too slow to emulate the consoles CPU or GPU in real time, so the emulator works a lot like JIT to translate say PowerPC instructions into x86 instructions on the fly.

author: tggzzz
author_rank: Super Contributor
timestamp: July 30, 2020, 06:39:25 am
content: Quote from: mfro on July 30, 2020, 06:32:46 am(Looks like your response and my addition to my post missed in the æther. )You do, of course have to ensure that there are the necessary memory barrier instructions in the JITter.Clearly theyareeffective, since JITters work. They are not, and do not need to be, lightweight. Since I haven't looked at this topic for 25 years, I am out of date on the details.

author: Berni
author_rank: Super Contributor
timestamp: July 30, 2020, 06:50:41 am
content: Oh and instruction cache is not a problem because the CPU itself is writing to it so the cache controller can see a write happening to that location and act accordingly.Issues with the cache comes up if you are to use other things like the DMA to modify it. In that case the write access didn't go trough the CPUs own internal memory bus and so the cache controller could not see it, so it keeps caching the old invalid data.However it seams like PowerPC has an issue with old instructions getting wedged in its pipeline, so even with cache disabled there are special procedures in making sure self modifying code doesn't screw it up.https://stackoverflow.com/questions/10989403/how-is-x86-instruction-cache-synchronized

author: mfro
author_rank: Regular Contributor
timestamp: July 30, 2020, 07:01:13 am
content: Quote from: tggzzz on July 30, 2020, 06:39:25 amA memory barrier doesn't help with cache coherence. In its original sense, memory barriers are only meant to enforce a certain sequence of execution of codeJIT will be effective on platforms that implement bus snooping to maintain cache coherence. Typically not found on µCs

author: james_s
author_rank: Super Contributor
timestamp: July 30, 2020, 07:06:11 am
content: I've dabbled in 6800 assembly with my Heathkit ET-3400. I created a FPGA version of the ET-3400 that anyone can play around withhttps://github.com/james10952001/Heathkit-ET-3400The 6800 core is not cycle accurate so programs run a bit faster than they do on a real ET-3400 but I've used them side by side and it's pretty close.

author: tggzzz
author_rank: Super Contributor
timestamp: July 30, 2020, 08:26:32 am
content: Quote from: mfro on July 30, 2020, 07:01:13 amBoth those points are true. Nonetheless, your original contention that self-modifying code won't work on modern processors isn't true.Cache coherence is found in any general-purpose SMP computer, and is a severe limitation on scalability.

author: MK14
author_rank: Super Contributor
timestamp: July 30, 2020, 08:37:02 am
content: Quote from: tggzzz on July 30, 2020, 08:26:32 am>>"self-modifying code won't work on modern processors isn't true".Many (not all) electronics/embedded processors (MCUs) these days, have Harvard architecture's, and the code can only reside in flash memory. (**Yes I know, some do let you run code in ram).I.e. No self-modifying code (ignoring continually rewriting flash memory techniques, which would risk wearing out the flash and would be relatively slow, continually erasing/rewriting the flash, usually).

author: tggzzz
author_rank: Super Contributor
timestamp: July 30, 2020, 09:19:04 am
content: Quote from: MK14 on July 30, 2020, 08:37:02 amTrue, but "modern processor" != "embedded MCU", any more than all black birds are crows.I remember seeing a C compiler get around the limitations of an instruction set by building a subroutine on the stack, and then execute it.

author: Nusa
author_rank: Super Contributor
timestamp: July 30, 2020, 09:26:54 am
content: To the OP that's just having fun learning, I recommend the following book:https://www.academia.edu/36492595/Prosser_The_Art_of_Digital_Design_2edThat digital copy is free, but a new or used paper copy is easy to buy if you prefer that.Disclaimer: I've known the authors my entire life as family friends and colleagues of my father. Also, I was one of those that did the labwork covered in the book (wirewrapping and testing their PDP-8 design) before the book was published. Winkel passed away four years ago, but Prosser is still moving. Dad died last October, age 88.

author: MK14
author_rank: Super Contributor
timestamp: July 30, 2020, 09:37:59 am
content: Quote from: tggzzz on July 30, 2020, 09:19:04 amDoesn't make any difference.Because I was effectively trying to say (in summary):SOME modern processors, don't allow self-modifying code.So, since many embedded MCU's are modern processors, what I said still stands and is correct.I.e. If some crows, have Covid. Then it would be true that at least some black birds have Covid.I would have needed to say, if ALL crows have Covid, then I couldn't say "ALL black birds have Covid", as that would not be necessarily correct.

author: Berni
author_rank: Super Contributor
timestamp: July 30, 2020, 09:44:52 am
content: Hraward architecture MCUs are sort of the old way of doing things these days. But yes self modifying code is indeed problematic for those.These days modern high performance MCUs are moving away from dedicated flash. The latency is becoming too high to directly execute instruction by instruction from it, so time critical things like interrupt handlers are meant to be put in RAM anyway. Also the manufacturing process optimized for CPUs does not make very dense flash, so megabytes of flash tend to start getting bigger than the CPU itself. For that reason they are now starting to make MCUs with no non volatile memory at all, requiring external memory to hold the program in things like QSPI flash, so copying critical code to RAM becomes even more important.Loading code from some other memory to RAM and then executing it is technically self modifying code. Tho its more appropriate to call it a bootloader instead.

author: tggzzz
author_rank: Super Contributor
timestamp: July 30, 2020, 09:55:12 am
content: Quote from: Berni on July 30, 2020, 09:44:52 amPrecisely.That can be extended to loading a program from disk in any conventional PC/workstation/supercomputer.

author: joeqsmith
author_rank: Super Contributor
timestamp: July 30, 2020, 11:10:19 am
content: Quote from: Berni on July 30, 2020, 09:44:52 amI'm sure many of us have swapped memory on the Intel parts. On my oldest computer, which had 2K of core, half for the video, you would have a small bit of code that would reside in core and it would then swap segments from the floppy to the core to execute them.On that computer, the bootstrapping was done in hardware. There was a reset button for the program counter. You would then press keys and the computer would take the lower nibble and combine it with the next keys to form instruction. Nothing would be on the display while you performed this process.

author: peter-h
author_rank: Super Contributor
timestamp: July 30, 2020, 01:39:38 pm
content: Self modifying code obviously needs to reside in RAM, but this is not that unusual in modern applications e.g. where you want the code to load an updated flash image. You normally can't run code from flash while writing the flash. On the ST ARM is supposedly does work, somebody claims, but you get about 100000 wait states after each instructionSo not practical. You need to copy the flash loader into RAM and run it from there. Some variants have two flash banks for this reason but they cost more.None of this stuff is a problem if you comment it properly. Nowadays most "software" is hacked really fast, with zero comments. It may be a form of job security for the programmer... Certainly in PHP it seems normal to have zero comments. Nobody can maintain such code cost-effectively... Commenting is an art, as much as writing the code itself.I am fairly sure the ST ARM 32F etc, running at the max of about 150MHz, runs out of the on chip flash with zero wait states. It would be dumb otherwise. External bus memory (which robs you of most IO pins) uses a load of wait states.

author: Berni
author_rank: Super Contributor
timestamp: July 30, 2020, 03:09:47 pm
content: Yes above around 100MHz the flash tends to start having latency issues keeping up with the CPU. It depends a lot on the implementation so it varies between vendors. One of the common vendors like ST uses cache accelerated flash for running a ARM Cortex M4 core at 180MHz. All of the larger chips from ST using the ARM Cortex M7 and similar then have a full on instruction and data cache right in the CPU. The fastest H7 family doesn't even run the internal RAM at full CPU speed anymore, so if you want to actually make proper use of all of those 480MHz you need to use the cache. Indeed some also have external RAM capability and caches are a must there, especially because DRAM likes sequential access and often these RAM controllers commonly found on MCUs tend to be rather slow (But still very useful for doing graphics at higher resolutions). At that point optimizing code indeed becomes more like on modern x86 PCs where the bottleneck is RAM latency so a cache miss becomes the most painful performance hit.Id say running from flash while also writing to it is a pretty common feature in MCUs even old harward based ones. More of a trap is that flash needs to be erased in large pages, so if you try to modify a location in flash that is too close to currently executing code then the erase step will also erase the code and crash before it has time to put that data back in. Doing this write while running from RAM with interrupts disabled gets around this problem. The fact of running slower is usually not really a problem since your flash writing routine is likely waiting for the slow flash to finish writing anyway (And its often a bootloader or something where it doesn't have to do anything else). Tho its better practice to simply put the data you want to modify somewhere far away and align it to the flash pages.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 30, 2020, 05:53:10 pm
content: Quote from: peter-h on July 29, 2020, 03:14:17 pmThis isn't actually true IMHO, all of this is easily controlled using the volatile qualifier in C. I have always written startup code in C, it's typically copying from one address space to another (so assigning *something = *something in a while loop), zeroing some address space (*something = 0 in a while loop), possibly configuring a few registers (just reg = val), then calling main. None of this requires asm.Most of your other points I agree with, though.The reasons forneeding(instead of just preferring it for the "why not" argument) asm modules or inline asm segments are usually so specific, weird and complicated that you could write a book chapter of each case, but yeah, the gist of it is that sometimes it is just impossible (or very difficult or unreliable) to get the C compiler to do what you need to do. In the end, maybe 1% tops of my MCU code is in asm, including startup code where I have never needed any asm.One example of inline asm would be a 2-channel synchronous DC/DC converter where the two interrupt handlers would store 16-bit current readings in a reserved (prevented the C compiler from using it anywhere in the program) CPU register so that SIMD instructions can be used with the least amount of memory overhead, so that both ISRs calculate values for each other as a side product of calculating 16-bit values for themselves.You can do many of the typical asm use cases with compiler intrinsics these days, though. It's a matter of taste whether you call this "asm programming"; it technically is, in a sense! The piece you need to do in asm gets progressively smaller when you move from the full asm modules into inline asm, then intrinsics.

author: ebastler
author_rank: Super Contributor
timestamp: July 30, 2020, 06:57:24 pm
content: Quote from: Nusa on July 30, 2020, 09:26:54 amWe seem to have lost the OP a while ago. More specifically, we lost him right after his original post. Maybe the fascination didn't last.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 30, 2020, 07:09:35 pm
content: Quote from: Berni on July 30, 2020, 03:09:47 pmNo, these STM32H7 (even the lower-end F7 series) parts do have the core-coupled RAMs that run at the CPU frequency, so no need to run with caches. Cache becomes very useful when you have large applications you want to run off SD card or something.The whole idea of running code from RAM benefits significantly from using a dedicated RAM just coupled to the core, so that instruction fetches do not appear on the bus. From chip design viewpoint, this is a relatively small extra cost to having a single larger RAM. The SRAM kilobytes themselves are expensive. (Of course, dividing the total kilobytes to several different interfaces makes the usage of the RAM less flexible if you need large tables or dynamic allocation.)In my experience, the ITCM / "boost" ram / scratchpad / whatever they call it sections tend to be so large (for example, 64kB for the H7 parts I have worked with) that you can fit most of your code there, not just the most obviously timing-critical pieces, making the decision what to put there much easier; place it in ITCM unless you are sure the performance doesn't matter. All sorts of init functions tend to remain in flash for running, for me.

author: MK14
author_rank: Super Contributor
timestamp: July 30, 2020, 07:18:29 pm
content: Quote from: Siwastaja on July 30, 2020, 05:53:10 pmNice post, overall, but 'volatile' is not always enough. The thing is that some hardware setup, has strict timing 'rules', imposed by the datasheet and/or hardware design.E.g. An A to D converter, which insists, that after enabling it, you have to wait X microseconds or machine cycles, before using it. So that it can stabilise/initialise itself properly.Or a watch-dog timer(s), which can only be enabled or disabled, a certain time (e.g. clock cycles), after initially coming out of reset. To stop errant software from being able to interfere with the watch-dog timer, later.In assembler, these timings, are fairly easily bolted down, by counting the clock cycles (which isn't necessarily that precise these days, as already discussed in this thread).But in a highly optimised compiler (although one option, might be to disable or reduce the optimisation setting, for the hardware initialisation part of the code). The compiler may interfere too much with the timings. Bit-banging, hardware timings, can also be tricky, when compiler optimisations are enabled.In some extreme cases (I remember worrying about this a long time ago, I can't remember if I had to do it or not, in the end). Even the initialisation performed by the C compiler (e.g. clearing sections of memory, compiler dependent), can take so long (in fast hardware terms, like milliseconds). That you can't meet the specifications of the product, without putting assembler start up code, even BEFORE the C code has initialised itself.Even before the first C statement, can be executed.E.g. To check for some startup options (defined by the statues of some external I/O port pins), then do things and/or disable/enable other I/O things. To meet those requirements.I can't remember exactly, but off the top of my head, it was something worrying long, like 500 milliseconds (on a relatively slow processor, too long ago, I could be misremembering, but it was longish), to finish the C compiler initialisations.It was to switch on or off, some stuff, to protect the hardware, immediately after switch on (coming out of reset). Otherwise, the hardware could damage itself (arguably poor design, not my design anyway).Anyway, I agree. that there are various things you can do, so that if you are really determined. You can solve most/all of these issues and implement such schemes in C code.E.g. Using hardware timers and/or interrupts. To get the timing right and independently of the instruction execution times.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 30, 2020, 07:27:28 pm
content: MK14,Indeed, setting up the C "runtime" for running standard-compliant C does take time. More specifically, this means that because the C standard guarantees that global (or static, inside of functions) variables, tables etc. are all initialized zero, there must be some piece of code before main() that zeroes all these variables. Similarly, there must be a piece of code which copies the initial values of all initialized globals and statics from the flash memory. That's basically what the magical "startup code" is - completely trivial.However, you can decide not to use the compiler-generated startup code and do that yourself - either in asm or C. This should be possible in most compilers.Clearing the bss and copying data in ASM isn't going to magically be any faster than the same in C, the implementation is trivial, and C compiler does the most optimized copy and zeroing, likely even at the lower optimization levels.You can, of course, decide to do things like perform certain important tasksbeforebss is zeroed or data copied - I do that all the time, you basically just move the important main() things out of main, to the reset vector handler or whatever you call it -, or decide not to conform to the C standard and not clear .bss, and so on, but you don't need any ASM to do any of this - the same result can be achieved in both C or ASM.That really leaves us with those peripherals that require cycle-accurate control sequences. I find those are exceedingly rare although yes they do still pop up from time to time, and using some asm to access them has better chances of being consistent than writing some dummy operations in C and hoping it stays the same.

author: MK14
author_rank: Super Contributor
timestamp: July 30, 2020, 07:38:00 pm
content: Quote from: Siwastaja on July 30, 2020, 07:27:28 pmThat particular example, was from a very old compiler (compared to ones, these days). It was an 8 bit processor (8051/2 series), and those compilers, were trickier and somewhat/potentially eccentric (the 8051 can be a funny sort of beast, e.g the way its I/O ports work!). Some old 8 bit processors (like that), with old compiler technology, were not especially brilliant.It sticks in my mind, because I remember the difficulties, that needed to be sorted out.

author: MK14
author_rank: Super Contributor
timestamp: July 30, 2020, 07:43:39 pm
content: Quote from: ebastler on July 30, 2020, 06:57:24 pmI noticed this as well, a long while ago. But decided, the thread is of general interest, and seems to be a fun and educational experience. I'm pleased with the information in it, and have enjoyed participating and reading it.We were at least a bit off-topic as well. (Opinion dependent!).So, if they had sticked around, it may have taken a completely different course.I suspect, in a number of cases, you get the following:[I'm really revving to go, and want to do assembly language programming, it must/will be great fun]..>>>Tries their first assembly program, 8 lines long, takes 38 minutes, to get rid of 5 assembly errors, then runs it and it crashes for no apparent reason...[Gives up and moves on to something else]

author: KL27x
author_rank: Super Contributor
timestamp: July 30, 2020, 07:47:24 pm
content: At least the title of the thread is appropo. I wonder how long the active oscope thread on the forum will be "what scope for a woman passionate about electronics."Everytime I see that thread, I imagine an oscilloscope that is strong enough for a man, but is purple and vibrates and discreetly slips into a purse.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: July 30, 2020, 09:38:33 pm
content: Quote from: tggzzz on July 30, 2020, 09:19:04 amFascinating, compiled ROP?Or was the stack somehow executable? (Is this not a ROM/Harvard machine as context would suggest?)A note about Harvard architecture: it won, but really, both (+ Von Neumann) won: all you need is an MMU with r/w/x tagging. If w and x are exclusive, then you have a Harvard machine at any given point in time, but you can always ask the kernel to toggle it back and write some more data. Which can be code or data, doesn't matter until re-tagging it again. (And perhaps this change could also trigger a cache flush or something, I don't know.)Tim

author: tggzzz
author_rank: Super Contributor
timestamp: July 30, 2020, 09:49:20 pm
content: Quote from: T3sl4co1l on July 30, 2020, 09:38:33 pmIt was the Whitesmiths C crosscompiler for a Z80 processor, possibly the 8080 subset. After 40 years the details are, fortunately, hazy. The long-term lessons are not!

author: MK14
author_rank: Super Contributor
timestamp: July 30, 2020, 10:31:29 pm
content: Quote from: T3sl4co1l on July 30, 2020, 09:38:33 pmIf the computer/processor system, has got only one single, shared memory, for instructions and data. (Von Neumann Architecture).Then, it will still only have one shared memory bus, for instructions and data, even if you start messing with MMU tags and things.It's the multiple, separate, non-shared memory buses. Which give Harvard Architecture, its potential performance benefits, because of having multiple bus systems, running in parallel.You could simulate it the way you described. But, it would give little or no speed improvement, and wouldn't really change much. if you needed the extra bandwidth/performance from Harvard's extra bus system(s).These days, the distinctions, seem to be getting blurry. So, even a Von Neumann Architecture, can achieve huge performance. E.g. Modern PCs.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: July 30, 2020, 10:35:14 pm
content: Right, with a single bus, you don't have the hardware advantage. You might call such a system a "virtual Harvard architecture".With modern machines, low level buses are very wide, and they don't care what data are being moved about; ultimately it gets into the caches it needs to, and those serve as a physical barrier again.Tim

author: MK14
author_rank: Super Contributor
timestamp: July 30, 2020, 10:45:29 pm
content: Quote from: T3sl4co1l on July 30, 2020, 10:35:14 pmYes, agreed.

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 31, 2020, 03:19:37 pm
content: Just a side thought - sorry if it has been mentioned already - but many modern CPUs (even small MCUs these days) have some form of cache, and quite often separate cache for data and instructions. So even if it has a single address space for code and data like in a typical Von Neumann architecture, from the CPU's execution unit POV, it actually has two separate buses for code and data. This has obvious performance benefits, but also has some security implications.Likewise, quite a few processor architectures these days are fundamentally Harvard but with some additional bridges allowing them to essentially be used as Von Neumann ones from a user's POV.

author: Berni
author_rank: Super Contributor
timestamp: July 31, 2020, 05:32:16 pm
content: Quote from: SiliconWizard on July 31, 2020, 03:19:37 pmThe harward architecture is not just about having a separate instruction and data bus. Its actually about having a separate memory addressing space for instructions and data. You can have machine code at 0x100 in the program memory while 0x100 in data memory actually points to RAM instead. As a result of this a harward architecture CPU tends to have separate instructions for working with program or with data memory. Typically your usual MOV,ADD,XOR..etc instructions with addressing operands only work with data memory while there is a special instruction that loads a program memory location into a register.This is what makes this architecture annoying for compilers since you can't just simply have a pointer to anywhere in memory. So this gives C compilers some oddities, like placing string constants into RAM so it can get a pointer to it, or introducing a special pointer type that works in program space instead.If you go down deep enough then pretty much every CPU splits its bus into data and instruction somewhere. At some point there is a bus feeding the instruction decoder where only instruction data is fed in. It just so happens to join the main bus very early trough some latch that is only activated when an instruction comes along on the multiplexed instruction+data main bus. So an instruction cache is just simply placed in a convenient spot where only instructions flow trough so that the cache can be optimized for the access patterns that instructions use(or take hints from a branch predictor)

author: T3sl4co1l
author_rank: Super Contributor
timestamp: July 31, 2020, 09:03:12 pm
content: Ironically, x86 has a Harvard architecture IO bus -- multiplexed on the same main bus, but accessed in the instruction set by two special instructions (IN and OUT, and their later extensions for word width and repeat (string) access). And a much smaller bus, too (not even the full 16 bits presented by the CPU, though, I forget how and where this was done on the PC-XT?).PC IO ports haven't completely gone away of course, but by many orders of magnitude of data flow, memory mapped registers and data have decidedly won out.Tim

author: T3sl4co1l
author_rank: Super Contributor
timestamp: July 31, 2020, 09:28:23 pm
content: Something of an aside, but related: an IO bus is just another set of bus strobe signals. So the x80/x86 family has theirs. 68x doesn't (all memory mapped, AFAIK?). Others have banked memory (and you could put IO into that just as well). Or you could reserve entire segments of address space for various purposes.A more general interpretation, then, could be considering any of the signals as bus enables. The CPU asserts some combination of them; and this is now in binary rather than unary coding. That is, the traditional multiplexed memory/IO space uses single strobes to select between them; say we have N address spaces: for N > 3, it's more efficient to use lg(N) address lines and a single strobe (and a lg(N)-->N decoder at the destinations) to do the same thing.So we might consider an arbitrary number of common-bus address lines as bus-select lines for our various buses.A banked memory system extends this further by using a sequence of operations with a stateful bus to make the selection. These are already familiar to many IO devices, where for example one register's value serves as the address to the second register.Tim

author: westfw
author_rank: Super Contributor
timestamp: July 31, 2020, 10:51:33 pm
content: QuoteHarvard is usually about Instructions vs Data. I don't think IO Buses count...

author: KL27x
author_rank: Super Contributor
timestamp: July 31, 2020, 11:25:17 pm
content: If I ever find myself disagreeing with Tim, my default conclusion is I don't know enough to understand how he is right.

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 31, 2020, 11:25:45 pm
content: The Wikipedia article more or less details what I summed up above :https://en.wikipedia.org/wiki/Harvard_architectureThese days, most CPUs are actually some form of "modified Harvard" rather than pure Von Neumann architectures.Not that it matters much, since the original definitions applied to very simple architectures - most modern CPU architectures are much more complex in comparison.

author: eti
author_rank: Super Contributor
timestamp: August 01, 2020, 12:04:42 am
content: Quote from: ebastler on July 30, 2020, 06:57:24 pmNo no, I am still browsing, I just have a real life outside forums, you know? There's only 24 hours a day, and I can't spend all of them onlineBesides, I bow to the superior ASM knowledge of all thread contributors; suffice to say that you guys appear to have infinitely more experience than me in this area, which is new to me for now, and I enjoy reading as and when I get 5 mins off to come here.

author: eti
author_rank: Super Contributor
timestamp: August 01, 2020, 12:12:06 am
content: Quote from: MK14 on July 30, 2020, 07:43:39 pmI NEVER give up! I may "temporarily set-aside" an interest, no matter how fleeting my current interest in it, is, and allow my life to continue, and allow whatever I've learnt up until that point, to subconsciously absorb into my mind and digest for however many weeks, months or years, but my interest is still there. I may not be au fait with this subject, but I have returned, re-visited my ASM interest, failed a few times to grasp... but it's truly absorbing into my mind now

author: eti
author_rank: Super Contributor
timestamp: August 01, 2020, 12:12:50 am
content: Quote from: MK14 on August 01, 2020, 12:09:47 am?

author: MK14
author_rank: Super Contributor
timestamp: August 01, 2020, 12:15:36 am
content: Quote from: eti on August 01, 2020, 12:12:06 amIf you start a thread, and people take the time to answer your original question(s). It is the polite thing, to return to the thread in a timely manner, and then respond to it.I get a bit agitated when people start a thread, then disappear, either permanently, or for very long periods of time.

author: eti
author_rank: Super Contributor
timestamp: August 01, 2020, 12:18:46 am
content: Quote from: MK14 on August 01, 2020, 12:15:36 amWell, here I am reporting in for duty, Sir!What is my penalty - do I get bread and water rations tonight, or will you let me off?You do understand it has (barely) been FOUR DAYS since I started the topic? Hardly an eternity. I am sorry you feel the way you feel, see if you can find a family member to hug!

author: greenpossum
author_rank: Frequent Contributor
timestamp: August 01, 2020, 12:21:48 am
content: Quote from: eti on August 01, 2020, 12:04:42 amThat's just the way EEVblog is, every discussion will eventually turn into a exposition by experts of angles far outside the scope of the original query or observation. Often educational but sometimes overwhelming. So I look at the reply count of a topic and if it's large, it's probably not worth entering. If there is a post worth reading, a targeted search will find it better.

author: MK14
author_rank: Super Contributor
timestamp: August 01, 2020, 12:23:21 am
content: Quote from: eti on August 01, 2020, 12:18:46 amI can only express the opinions of one person. Let's see what others think.My perception was that this thread was started some 2 weeks ago. I'm amazed it was started on the 26th July.Human perception, can make mistakes, like mine just did, sometimes.

author: eti
author_rank: Super Contributor
timestamp: August 01, 2020, 12:25:51 am
content: Quote from: MK14 on August 01, 2020, 12:23:21 amIt's alright, don't think any more about it, we all make mistakes my friend - it's a TOTAL non-issue to me

author: Ian.M
author_rank: Super Contributor
timestamp: August 01, 2020, 04:31:21 am
content: @Eti,So now you've had a chance to catch up, where do you want to direct this thread to?Quote from: eti on July 27, 2020, 02:54:44 amIMHO that's best achieved by programming in assembler with the big thick core instruction set 'bible' for your processor open beside you, though reading other people's well commented code can also help.However, jumping right in at the keyboard and coding freehand is counter-productive*- that's how you make a mess of spaghetti code. As ASM has so little structure, you must always code with clear objectives, develop well defined interfaces between modules of your program, and keep track of side effects (e.g. registers a subroutine 'smashes'). An hour designing appropriate data structures is worth ten or even a hundred hours coding.* Early in learning a particular CPU core free coding routines small enough to grok in their entirety, then stepping through them either under a debugger or dry-running them has its place, but if you have to scroll the editor window or shuffle pages to find a branch target its getting too big to reliably comprehend.One thing that's essential is a quick reference card that shows which registers and flags are affected by each instruction, as especially on CPUs with minimal working registers, you will find yourself testing something, doing <something else>, then attempting a conditional branch on the result of the test. Its nice to be able to pull it off to avoid having to duplicate the code <something else>, but of course <something else> mustn't smash the flag bit(s) needed for the conditional branch.On the subject of evil instruction sets: the baseline (12 bit) PIC core cant return from a subroutine without smashing W (its accumulator and sole working register) as it doesn't have a RETURN instruction. It only has RETLW (Return literal in W), so any subroutine that needs to pass back a value in W has to have multiple exit points, one for each possible value!

author: RJSV
author_rank: Super Contributor
timestamp: August 01, 2020, 04:49:43 am
content: Responding here with noted differences, 6502 andZ-80 compared:. Both types have same math, so that helps, if writing ASM code. Now, 6502 and 6800 are termed 'memory mapped' (the registers holding active variables).Writing ASM with a Z-80, that CPU is termed as a dedicated register type, (not an industry term, exactly).So, for example a block move on the Z-80 will have formal requirements, such as 'put count in such and such (B and C up to 64k counts). Formally, put source pointer in HL (again 64k range).In your 6502 ASM world, the formality is maybe more limited in that the highest priority 'registers' are few.But, and this is the magic of the 6502, you get a huge number (256) of next-highest registers; so many as to allow easy assignment of a couple HUNDRED variables, just sitting there for dedicated and rare use. Who cares, if register #117 contains a count of how many wombats fly East every hour, my Wombat subroutine is just going to keep a privately owned bin there, maybe only reported each month, or some dang thing the person writing the ASM wanted. You see the maintaining or separation of function, but without swapping things around, to prevent harmful mixing of Wombats with 'Zebras', in on-going accumulation of data results.Uh, not clear, huh? So consider in using a solar system analogy. A Z-80 might be like having a SUN with 4 planets, plus a swarm of meteorites way out. The access to the 4 very-near planets is excellent, while the further off asteroids are required as well, as the highest priority (Z-80 registers) are very limited in quantity and thus likely transient.A 6502 however, has next to highest working registers residing as 256 'page-zero' mapped', thus maybe permanent, meaning some manipulations (increment decrement) can occur directly and be done.Then, even more mapping allows a third tier mapped' into the space from 256 thru 64K bytes.A sort analogy of a planetary system having 3 rings of multiple planets.Rescue of Z-80 CPU preference comes largely from the 16 bit functions closely accessed. But I'd bet many factors such as having dedicated Z-80 support IC's such as Z-80 CTC, Z-80 SIO and a plus for looking like conventional expectations, maybe of some VP of Engineering somewhere.The , 6502, can wrong or right, look a bit flakey, considering emerging 'hobby' image.Uh, how was this, then? (I think 34 readers just quit and became antique furnishings dealers.)-thanks, RJ

author: tggzzz
author_rank: Super Contributor
timestamp: August 01, 2020, 09:43:08 am
content: Quote from: greenpossum on August 01, 2020, 12:21:48 amIt is not just eevblog. I've seen the phenomenon for 35 years; it started on usenet and continues there and elsewhere.

author: MK14
author_rank: Super Contributor
timestamp: August 01, 2020, 04:44:33 pm
content: Quote from: eti on August 01, 2020, 12:12:06 amNow the dust has settled a bit.Yes, assembler, can be a rather hard and daunting subject, to master.It can look so, temptingly easy, with each assembly instruction, only doing a very simple and quick operation.E.g. INC A .. Increment Accumulator by 1, so 57 becomes 58 and so fourth.NOP .. Do absolutely nothing this machine cycle(s).CLC .. Clear Carry flagBut the real difficulty, is realising how, using these very simple building blocks. You can create a powerful, working program from them.Speaking from experience, I'd recommend, doing 3 things initially..(1) Practice(2) Practice(3) PracticeDo all 3 things, don't skimp and only do one or two of them.There probably are some good books on how to go from beginner, to Assembly language programmer.If you have the time, patience and desire to read books like that.EDIT: Thread Cleanup - Removed later posts - To hopefully get back on-topic.

author: RJSV
author_rank: Super Contributor
timestamp: August 01, 2020, 08:31:01 pm
content: More thoughts, on 6502 vs Z-80:Very old-school, but my guess is PIC programmers might benefit from this thread. Now comparing implementing an indexed list, there is an interesting reversal. With Z-80 ASM code, you use a base value, in IX register, that's 16 bits for a 64k total RAM space, and plus a fixed offset of 8 bits. Visualize IX,14 for example. Now with a 6502 it is reversed: There is a fixed BASE, plus a variable offset held in a register.So that case visualize 20,000 plus contents of 'Y' register. That's the reverse case, as you get a total RAM of 64k, but the relevant block is 256 bytes. Sounds more natural, and practical. Hopefully, my presentation will precede some helpful ASM learning, I always used that difference as a memory tool, thinking first that the (two) processors had reversed scopes, and then I could recall the exact structure, and thus write code appropriately.However, a project partner disagreed with convention,writing SELF-MODIFYING code. In horror, I nastilly spoke "You must never, ever write self-mod code, it won't work from a ROM". He was pulling out the 2-byte base value, from the 6502 ASM object code, figuring to increment that, for access to a larger, 8k block (C-64 bit mapped' graphics). But it worked, so...I wished for better people skills as there was arguing around that. But now, maybe some legacy projects have to deal with issues, of non- writable ROM, in year 2020 legacy re-issues. (Can always copy to a RAM buffer, for correct function, in any hand-held legacy gamer.) I think, ultimately, the other guy was right.By the way, a 6502 could do a 16 bit alteration, by a few lines; change the low byte and bring any overflow / underflows to the high byte, but there are several linesof code just to retrieve and store. Probably, the guy was thinking Z-80, while making 6502 code! But it works, and I felt like a snob, "his code inferior or dodgy". Humans are complex, imperfect.While reading such, some learning takes place; how to write code, either 6502 or Z-80, by playing each system concepts against the other. I think one justification was, we can re-write for a hypothetical, when it comes real. I have to respect that.Oh, and I would use caution w an Altair Simulator, regarding exact real-time clock states, it may not strictly follow the 'legacy' functions. If I recall, the Z-80 could run on a 4 MHz clock, using 4 clocks per (instruction). The 6502 often used a 1 MHz clock, with one clock per instruction.Injecting some personality into the process is a good memorizing tool, that's why the 6502 and Z-80 can play off each other...

author: T3sl4co1l
author_rank: Super Contributor
timestamp: August 02, 2020, 01:14:13 am
content: I like to approach optimization from the compiler's side, and go from there. Work in stages:1. Any high-level optimizations (to the algorithm) have already been done. (Start there first!!)2. You can write your ASM function to drop in, so the compiler knows how to use it and you can go back to #1 if a new strategy comes up. (You may end up discarding that function in the process, wasting effort -- hence the emphasis on the front end.)3. While you still need to comply with the compiler's contract for call/return conventions (the ABI), you have complete freedom to (ab)use the hardware within the function. If you can make better use of it, there you go, that's clock cycles saved!4. If it looks like #1 is basically done, you can go farther and expand into other functions, optimizing neighboring ones, inlining them in a longer or higher-level function, etc. (The compiler does some of this already, but it won't discover many optimizations it couldn't have already made in the base functions.)Case study: a simple DSP (digital signal processing) operation. The basic fundamental of DSP is the multiply-accumulate (MAC) instruction, A = A + B * C, used in a loop to convolve two arrays. (Convolution is a big word for a simple process: for each element in two arrays, multiply the elements pairwise, then sum up their results. If you know linear algebra, it's the dot product of two vectors. This isn't a full definition of convolution, more a functional example.)For example, if we convolve a sequence of digital samples (the latest sample, and the N previous samples), with a sequence of samples representing the impulse response of a filter (also of length N), the result is the next sample in the series of that signal as if it were passed through that filter. This is a FIR (finite impulse response) filter: an impulse input (one sample nonzero, the rest zero) gets multiplied by each element of the filter in turn, all of which can be nonzero, up to length N where the magnitude implicitly drops to zero, because, well, that's the size of the array.FIR filters are great because you can control the impulse response, well, exactly; that's all the filter array is! And by extension the frequency response, and it's very easy to get certain minimum-phase or equal-delay properties from them. The downside is, if your desired filter has a very long time constant (e.g., a low frequency, narrow bandpass/stop, or long time delay or reverberation), you need as long of an array. And as much sample history. And you need to compute the convolution every sample.So, DSP machines need to crank a lot of data, and tend to be very powerful indeed, if relatively specialized for this one task. (Example: one might use a 200MHz core capable of delivering one MAC per cycle, so could process around 4500 words per 44.1kHz audio sample; a FIR filter could have a minimum cutoff on the order of 20Hz.)If we use a history of inputand outputsamples, we can employ feedback to useful effect. We have to be careful, obviously we don't want that accumulating to gibberish, with exponential growth; it has to be stable. And being a feedback process, we expect it to have an exponential (well, discrete time, so technically, geometric) decay; an infinite impulse response (IIR). Whereas the FIR filter coefficients can take any value, the IIR filter coefficients have to be computed carefully. Fortunately, that analysis has been done, so we can design filters using tools, without having to get too in-depth with the underlying theory. (Which revolves around the Z transform. It happens to map to the Fourier transform -- everything the EE knows about analog signals, already applies to DSP, if in a somewhat odd way.)Anyway, that explains the basic operation. How do we compute it?Here's a basic MAC operation, taking as parameters, a 32-bit "accumulator", a 16-bit sample, and an 8-bit coefficient. (This wouldn't be nearly enough bits for a proper filter, but works well enough for a simple volume control -- we might convolve arrays of live samples, with gain coefficients, to create a mixing board. We can fill the arrays however we like, after all; the convolution doesn't have to be across time, it just is when we are filtering a signal.) The format is integer, signed, presumably in fixed point. (A typical case would be both sample and coefficient in fractional format (0.16 and 0.8 ), so that the result is 8.24, and the top 8 bits are simply discarded, after testing for overflow of course.Code:int32_t mac32r16p8(int32_t acc, int16_t r, const int8_t* p) {return acc + (int32_t)r * *p;}In avr-gcc 4.5.4, this compiles to: (comments added inline to help with those unfamiliar with the instruction set)Code:mac32r16p8:push r14push r15push r16push r17; save no-clobber registersmov r14,r22mov r15,r23mov r16,r24mov r17,r25 ; r14:r15:r16:r17 = accmov r30,r18mov r31,r19 ; Z = pld r22,Z ; r22 = *pclr r23sbrc r22,7 ; skip following instruction if bit in register setcom r23 ; bit 7 = sign bit; com = complement -- ah, this is a sign extend operationmov r24,r23mov r25,r23 ; sign extend to 32 bits (r22:r23:r24:r25 = (int32_t)*p)mov r30,r20mov r31,r21 ; mov r18,r30mov r19,r31 ; ?!?!clr r20sbrc r19,7 ; oh, sign extending r20:r21 = r...com r20 ; probably the compiler allocated r18:r19:r20:r21 at ,mov r21,r20 ; so it had to move to a temporary register (r30:r31) first. sloppy.rcall __mulsi3 ; r22-r25 * r18-r21 = r26-r31; 37 cyclesmov r18,r22mov r19,r23mov r20,r24mov r21,r25 ; ?!add r18,r14adc r19,r15adc r20,r16adc r21,r17 ; acc + productmov r22,r18mov r23,r19mov r24,r20mov r25,r21 ; return resultpop r17 ; restore no-clobber registerspop r16pop r15pop r14ret- Note the surrounding push and pops: the ABI says, don't clobber r2-r17 and r28-r29. This function uses a lot of registers (8 passed in, 4 passed out), so that might happen. Push and pop costs a couple cycles each (most instructions are 1 cycle, but most memory accesses add a 1-2 cycle penalty), so they're a priority to get rid of.- Most of the instructions are moves. Hmm, that's not a good sign. Why can't we get the registers in the right places, to begin with? Well, calling conventions are fixed by the ABI, nothing we can do about that at this stage, but there's still more shuffling going on than that.- Though, it looks like acc starts in r22-r25, and that's also where our output goes. Hmmmmm...- Also, the compiler has already made obvious boners: there is a MOVW instruction which copies registers pairwise; instead ofmov r14,r22; mov r15,r23it should've usedmovw r14,r22(and the rest).- It looks like everything is just setup to use the library wide-multiply call __mulsi3, a 32 x 32 = 32 bit multiply. This sounds terribly inefficient. I mean, for what it does, the library call isn't bad, 37 cycles for that much work -- but we were supposed to be doing 8x16 here. This is ridiculous!But it is standard practice, when the compiler encounters operations that would be too difficult to reason about. GCC will emit MUL instructions for byte multiplies, but anything larger uses library calls.Naturally, they don't have libraries for every possible combination, only the most common -- signed-signed, signed-unsigned and unsigned-unsigned at 16 and 32 bits each I think.Library calls also use a custom ABI, are never inlined, and never pruned (even with -flto). So the unused parts (sign extension and correction) waste code space, too.So let's have a go at this, eh? What can we get it down to? Here's what I have in my project:Code:mac32r16p8:movw r30, r18ld r18, Z+ ; get multiplier; r25:r24:r23:r22 += mul_signed(r21:r20, r18)eor r19, r19 ; use r19 as zero regmulsu r18, r20 ; p*lo (result in r0:r1)sbc r24, r19sbc r25, r19 ; sign extendadd r22, r0adc r23, r1adc r24, r19adc r25, r19muls r18, r21 ; p*hisbc r25, r19add r23, r0adc r24, r1adc r25, r19eor r1, r1ret- Yup, the push/pop can be removed!- Wait, how is this even so short? Yeah the compiler is wasteful, but heck! Well, inlining the multiply and stripping it down to the required 8x16 bit operation saves a hell of a lot of effort. The two sub-terms need to be added together (same way you do 1 x 2 = 3 digit multiplication by hand). The MULS(U) instructions return carry set if the result needs to be corrected for signedness; in essence we sign-extend the result, hence the sbc rx, 0's into the accumulator. It's a lot more addition than the 2-3 add/adc needed for an unsigned operation, but it's still better than adjusting for sign after the fact (i.e. using an unsigned multiply).- Clearly, the semantics of doing all this is more involved than just one instruction. Extra registers need to be allocated (MUL uses r0:r1 as implicit destination; and I've used r19 to hold zero). Probably there's no equivalent in GCC's internal representation, either (where most of the optimization is done). So they give up and call a library.- Conveniently, acc is passed in the same place the result is returned, so we can just accumulate directly into those registers. (This depends on parameter order -- swapping parameters alone may yield optimization!)- Only one instruction is needed to maintain compiler compatibility: r1 is used as the zero register, so needs to be zeroed after use.I further inlined this into the looping function. Notice theld r18, Z+postincrement instruction; r30:r31 isn't read after this, it's discarded instead. (There's no cycle penalty for ld Z vs. ld Z+ so it doesn't matter that I left it in there.) I can abuse this clobbered value of Z to just run this function in a loop, loading and accumulate all the products right there.But further, I can inline it, saving a few more cycles (loop overhead being less than call overhead + unrolled loop).Overall, the optimizations on this project yielded almost a tenfold improvement -- on the meager platform, it went from hardly worth it (two mixer channels, ~50k MACs/s) to reasonably featureful (two biquad filter stages and 8 mixer channels, ~500k MACs/s).Tim

author: KL27x
author_rank: Super Contributor
timestamp: August 02, 2020, 03:33:47 am
content: QuoteA lot of the cool new instructions are convenient, but in many cases we were achieving the same thing with a more basic instruction set, already.In this case, you can transfer contents of w to any register you want and retrieve it after calling the subroutine. The "new feature" mostly just saves code space (and 1 memory register) and execution cycles. Any given subroutine is going to change registers and/or ports. That's what they're for; that's all they do; that's what code does at the most basic level. The w register is just one more of many.You don't do retlw lookup tables because it's the only way to do it. You do it where it's convenient. We still use them in 14 bit PIC, because they're still convenient.;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;edited to add: if you don't have a return instruction, you use up one general data register and two lines of program memory after the call, and it's somewhat uncommon to need to do this in the first place. If you have only return and no retlw, your lookup tables will take up twice the program memory. E.g., instead ofretlw ['a']retlw ['b']retlw ['c']you would have to domovlw ['a']returnmovlw ['b']returnmovlw ['c']returnalso your BRW or addwf pcl instruction will need an extra instruction prior to it to double the value of indexing;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;In my own code, 90% of my "returns" could be "retlw [arbitrary value]." And I still use retlw data tables. When deciding what to cram into the 12 bit core, they left out the one of two instructions that was less valuable with the easier work around. "Return" is more expendable. The only place where it's not expendable is return from an interrupt. And here, you have retfie, only. You don't have retfielw (which here would be almost useless).Eti: everything you can do in C, you can do in assembly. But in more complex micros, writing assembly will make your spin and is extremely time consuming.

author: westfw
author_rank: Super Contributor
timestamp: August 02, 2020, 04:29:59 am
content: QuoteI've been somewhat amused that ARM CM0 doesn't have the "shifted constant" version of MOV, but that you can load a small constant and then do a shift, in the same amount of space. (you don't get the other weird modes of MOV constant, but ... they are less common.)

author: VK3DRB
author_rank: Super Contributor
timestamp: August 02, 2020, 05:59:03 am
content: Quote from: MK14 on August 01, 2020, 04:44:33 pmRubbish. Assembler is easy. Just read the manual that came with the assembler or macro assembler. These compilers are not hard to master.Assembly on the other hand can be a little hard and daunting for newcomers. A good place to start is read the datasheets and the programming literature for a particular microcontroller. Use an emulator or ICE or other debug tool. For Microchip, the PICkits are a good tool to step through your code to know what is going on. A "hello world" can be as simple as configuring a UART and echoing a character back; or just flashing an LED; or even just loading a register and reading it back.

author: Berni
author_rank: Super Contributor
timestamp: August 02, 2020, 07:53:24 am
content: Quote from: VK3DRB on August 02, 2020, 05:59:03 amIts one thing figuring out the tiny instruction set of a PIC16F where you have something like 35 opcodes in total. In fact its so simple that a single Wikipedia page covers all the information needed to write assembly for all the PIC families from PIC12 to PIC24:https://en.wikipedia.org/wiki/PIC_instruction_listingsThe modern x86 instruction set has >1000 instructions. There is a mountain of internal registers, lots of them sticking around for legacy reasons. In terms of execution time the instructions can have complex dependencies due to how the pipeline works. For example you get instructions like PMOVZXBW with a huge long opcode of "66 0F 38 30 " that just simply does "Zero extend 8 packed 8-bit integers to 8 packed 16-bit integers". Sure you don't need to know all of it and can get by with the basic move, math and conditionals but you will have a hard time beating C when it comes to writing a reasonably complicated algorithm. You need to know a LOT to be able to write assembler that is more optimized than what C spits out. While writing the C code does that is a lot easier and requires less knowledge (And can be applied to other architectures too).Still this is no reason to never touch assembler. Knowing the basics of how assembler works on your particular architecture can really help you understand what C has to do in order to make things happen. This makes it easier to understand the occasional oddities in C and help you write faster C code. If nothing else you can look at the disassembly and check any critical tight loops for any potential improvements. But writing whole apps in assembler doesn't make sense these days. The actual app tends to get complex enough already, so you don't want to make it even harder by having all the code look like endless unreadable towers of instructions with no format while having think about how to do something rather than just writing it out as a single line of code. You can still stick some inline assembler in a C function to hand optimize a fast loop if needed, but leave the mountain of other non critical stuff to C.

author: Ian.M
author_rank: Super Contributor
timestamp: August 02, 2020, 07:55:52 am
content: Quote from: KL27x on August 02, 2020, 03:33:47 amActually the baseline core doesn't even have RETFIE, because it doesn't have *any* interrupts!The interrupt capable enhanced baseline core (which very few PICs use, IIRC only PIC16F527, PIC16F570 & PIC16HV540) shoehorns an extra three instructions into the 12 bit core instruction decoder, including both RETFIE to support interrupts and RETURN, and also, to make it semi-usable, increases the stack to four levels.Quote from: VK3DRB on August 02, 2020, 05:59:03 amOn learning PIC assembly language by doing:A PICkit (2 to 4) is a great debugging aid *IF* your target supports debugging. To do so, the PIC must have hardware debug support built into its silicon*. Baseline PICs, many midrange PICs and some enhanced midrange PICs don't. Choose a debug capable PIC and enjoy learning by stepping through your code instruction by instruction, watching the actual effect of pins changing in your target circuit.OTOH The MPLAB IDE simulator is cycle accurate for the core. Its also reasonably good for supported peripherals, but a lot of the more interesting peripherals are either only partially supported or aren't supported at all. However, you can simulate anywhere, without having to be tethered to that big mess of wires and chips on your bench!* For some non-debug-capable PICs you can buy an expensive and fragile 'debug header', which includes a special debug capable version of the target, with additional pins bonded out to support the ICD interface on a footprint converter board. They are a PITA to work with on anything other than DIP footprint target boards. Unless you are backed into a corner avoid Microchip 'debug headers' as choosing a debug capable PIC in the first place with enough pins to reserve the three ICSP/ICD ones for programming and debugging is almost always a better option. You may say "but cheeseparing management demands I save $0.02 on the BOM" and forces you to choose a non-debug-capable PIC , but nowadays there's almost certainly an even cheaper debug capable enhanced midrange device that can do the job.

author: KL27x
author_rank: Super Contributor
timestamp: August 02, 2020, 08:18:36 am
content: QuoteOh, yeah. I keep forgetting that. Westf has pointed that out to me before a couple of times. I'm glad he refrained and allowed you, this time. I kinda moved on from baseline before even turning my attention to interrupts, and I somehow got the idea that they must have them, too. (The only reason I said that the 12F54/7 "fruit machine" code didn't have an ISR was that literally there was no interrupt code in the program. It didn't occur to me that it wasn't even possible, at the time I said it.)QuoteCool, yeah. It makes much more sense this way. That return and retfie are introduced together. Because at the core, I can't really imagine why retfie and return would need to occupy separate chunks of the core, at all. I wonder if they are really only distinguished by the IDE for error handling and semantics? I suppose once an interrupt handler is added, retlw is the more dispensable one of the two.edit: oh. Well, that only holds up until you add automatic context-saving. So you still need some etra core space to distinguish between return and retfie at that stage, but maybe not completely redundant. But without automatic context-saving (which many older PICs don't have) it seems like retfie and return should be interchangeable other than your IDE or assembler wouldn't allow it.

author: Ian.M
author_rank: Super Contributor
timestamp: August 02, 2020, 08:37:11 am
content: The enhanced baseline core kept RETLW (opcode 0x8nn). The only difference between enhanced baseline RETURN (opcode 0x01E) and RETFIE (opcode 0x01F) is one bit, which is needed for the instruction decoder to determine whether or not to set the GIE flag (to re-enable interrupts on RETFIE) and restore normal context. The 'classic' baseline core doesn't use any 0x01n opcodes.

author: KL27x
author_rank: Super Contributor
timestamp: August 02, 2020, 08:50:53 am
content: Musta added the edit to my post while you were typing.I'm sure some of the PICs I have used had no automatic context saving. Your ISR had to start by saving context (w, page, bank, STAT?), then restore that stuff before you retfie. The PIC templates included that for you; you have to leave it there to assemble (if you are using interrupts and want it to work).And they could technically have done the same with the GIE enable? Re-enable before retfie? Curious... Because now, you don't need separate retfie/return, do you? Or what else am I missing?... I suppose GIE bit would have to be globally accessible from all banks, if it must actually be the very last instruction before retfie, and maybe they couldn't use it or didn't have any more bits available, or I'm missing something more basic... hmm. reenable, interrupt before retfie executes, potential stack overflow?Sorry for the digression. To the OP, especially, lol. And this is why threads go on for pages with nothing to do with the OP.

author: Ian.M
author_rank: Super Contributor
timestamp: August 02, 2020, 09:29:43 am
content: Reenabling GIE would have to be deferred till after the next RETURN if you did it that way. I suspect that it would be simplest to update GIE when the next NOP is executed as RETURN 'NOP's out the following instruction in the pipeline while its restoring the PC from the stack. Therefore in your main code you'd BSF or BCF GIE followed by a NOP to action it. Active skips and GOTO also 'NOP' out the following instruction so they'd have to be avoided between setting GIE and the ISR RETURN.A bigger issue that made automatic context saving unavoidable was the choice forced on them by the lack of memory mapped SFR space (they'd already reduced the unbanked GPR space to only four bytes), to use a non-memory-mapped bank select register, so you couldn't read it back for context saving.

author: KL27x
author_rank: Super Contributor
timestamp: August 02, 2020, 09:40:25 am
content: I think I understand why, now. But what's really amazing is that you know such minute details, at all!? Incredible.I suppose that's where the "e" comes from. RETurn From Interrupt.... and reEnable.

author: KL27x
author_rank: Super Contributor
timestamp: August 02, 2020, 10:01:58 am
content: Quote from: Ian.M on August 02, 2020, 08:37:11 amI chewed on that. So that extra bit doesn't even get processed in the normal cycle. Because all 12 bits of address space are already used. So it only gets checked in the last branch of the core machinery which does the new return-that-conserves-w-register instruction plus or minus the GIE enable, after. I feel like I'm one step closer to understanding the flow chart thing depicting the hardware structure in the datasheets. In the beginning, I thought that day would never come, but now I'm not 100% sure it won't, some day. (And if I don't, it WILL be for lack of trying; I don't know why I'm even thinking about it.)

author: Ian.M
author_rank: Super Contributor
timestamp: August 02, 2020, 11:21:39 am
content: Take MPLAB 8.92 and select a PIC with the desired core and as much program memory as possible. Open the program memory view, and make sure the Machine tab (at bottom) is selected and the Disassembly column is visible. Next, right click and fill with a sequence, starting from address 0x000, opcode 0x000, and it will fill all memory with incrementing opcodes, show you how they map to instructions and where the gaps are. Some gaps are real, others aren't.e.g. the hardware doesn't have a CLRW opcode, it and CLRF are really special cases of the undocumented CLR opcode, which if it was treated as any other instruction with a target register would take a F or W modifier bit, so CLRW is reallyCLR <dont_care_reg>, W'under the hood'.Compare with the INSTRUCTION SET SUMMARY table from the datasheet and you'll start getting clues to the instruction decoder architecture.For further enlightenment you'll have to set up to execute undocumented opcodes on real hardware, feeding in preconditions and dumping results. If you've got the use of a full chip level reverse engineering lab so you can decap the die and microprobe it running or even use a scanning electron microscope in voltage contrast mode, you can make definitive conclusions, but most of us have to settle for educated guesses backed up by as much of the IC state as we can read back or infer.

author: Berni
author_rank: Super Contributor
timestamp: August 02, 2020, 03:24:42 pm
content: Quote from: MK14 on August 02, 2020, 02:38:20 pmNo need to get too pedantic about the terminology.If one is to be pedantic then a thing that takes C code and spits out a working EXE file can't be called a compiler. Even tho this is what pretty much everyone will understand when they hear compiler.The compiler just turns the C source files into object files. These things can't be executed yet. So they need to be fed trough an assembler to turn it into machine code. Yet you still can't run that because all you got now is a bunch of blobs of machine instructions. This needs to be fed trough a linker to actually put them all together into memory and hook them up together. So now you got a memory image that would run, but still can't be put inside a EXE. So it must be put trough a loader/output generator to package it up into an executable file along with all the information required to load it into memory and start it.But everyone just calls it compiling, even tho that's technically just one small part of the process.

author: Benta
author_rank: Super Contributor
timestamp: August 02, 2020, 06:28:40 pm
content: Quote from: MK14 on August 02, 2020, 04:06:07 pmHow? You're one of the contributors that derailed it completely. I understand why the OP has stepped back.

author: eti
author_rank: Super Contributor
timestamp: August 03, 2020, 12:35:28 am
content: Derailment *and* a train wreck in one fell swoop - amazing. How is it that people can't just keep shtum and let others have a viewpoint without "correcting" them?I'll leave you lot to argue whilst the contributors contribute, I'll pop along again soon and see if we've actually progressed with a coherent, calm interchange, at all...