flat assembler
Message board for the users of flat assembler.
Index
> Main > Simple51 as a learning exercise |
Author |
|
Melissa 04 Oct 2020, 20:25
I beleive you have 8051 assembler as include file for new fasmg...
|
|||
04 Oct 2020, 20:25 |
|
Hugh-Aguilar 05 Oct 2020, 05:02
Melissa wrote: I beleive you have 8051 assembler as include file for new fasmg... Well, I'm writing my own as a learning exercise. I rarely look at other people's code. I can't learn anything that way! Nobody will ever accuse me of being a maintenance programmer. Technology will never advance if everybody just copies existing code. This is stagnation. This is why the Middle Ages lasted for many centuries. For example, none of the assemblers I have written in the past (for the 65c02 and the MiniForth) restricted the user to one instruction per line. It is bizarre that all of the other assemblers in the world (including FASM) have this pointless and annoying restriction. Some CISC processors (the IBM370 and the VAX) allow an instruction to have one, two or three operands, so the assembler can't easily tell when the instruction is complete, which is why the assembler has the restriction of one instruction per line. That is ancient technology! In the 21st century, I would hope that we could stop copying 1960s assemblers that were written for long-obsolete processors and have corresponding limitations. Of course, a good argument can be made that if I did copy other people's code, I would not diverge so much from the standard way of doing things. This is how I do control-flow: Code: In a single-pass assembler you can't have forward jumps to arbitrary labels. Control-flow is done with code blocks that are bracketed with { and } like in C syntax. Code blocks can be nested. A code block has to be within a 2KB segment, not spanning the boundary between 2KB segments. Note that I use the term "segment" to mean 2KB and "page" to mean 256 bytes, which is non-standard terminology. In a code block, the following work: IF A conditionally jumps forward on accumulator zero to past code block IF C conditionally jumps forward on carry clear to past code block IF bit conditionally jumps forward on bit-variable clear to past code block IF= operand lit_number conditionally jumps forward on not equal to past code block (CJNE) the operand is A, @R0, @R1, Rx or a zero-page address FOR Rx decrements the Rx register and jumps to the code block beginning if Rx is non-zero (DJNZ) < unconditionally jumps to the code block beginning << unconditionally jumps to the parent code block beginning <<< unconditionally jumps to the grandparent code block beginning > unconditionally jumps to past the code block >> unconditionally jumps to past the parent code block >>> unconditionally jumps to past the grandparent code block Note that IF and FOR are 8-bit relative. If they fail for destination out-of-range, then the user has to fix his code. I encourage Forth-style factoring of functions into sub-functions, so code-blocks should be pretty small most of the time. The > >> >>> are all 11-bit absolute, so they won't fail. The < << <<< optimize as 8-bit relative or 11-bit absolute. I don't support 8-bit relative unconditional forward jumps --- this makes for a slight inefficiency in the code. In the following, xxx yyy and zzz mean some code: Forth has: IF xxx THEN For bitflags, I have: { IF bbb xxx } Forth has: 0= IF xxx THEN For bitflags, I have: { IF /bbb xxx } Forth has: IF xxx ELSE xxx THEN For bitflags, I have: { { IF bbb xxx >> } yyy } Forth has: IF xxx ELSE IF yyy ELSE zzz THEN THEN For bitflags, I have: { { IF bbb xxx >> } { IF bbb yyy >> } zzz } CALL optimizes as either ACALL or LCALL internally. The label has to be prior to the reference in the source-code. If you write your own OS, you should put each task in a segment so CALL will use ACALL as much as possible. The OS system functions would be at the bottom of code memory and require CALL to assemble an LCALL to reach them. Note that each task has its own vocabulary, so code label names can clash between tasks without a problem. Data labels, EQUs and system functions are in the common vocabulary. For semaphores, I have this: GRAB bit label conditionally jumps backward on bit-variable set to label, and clears the bit (this is JBC) GRAB is used in multi-tasker dispatching. The tasks are in order of priority and use GRAB to grab a semaphore and gain control. The dispatcher is an ISR on a heartbeat timer. If none of the tasks gain control, then the system goes back to sleep. The ISRs tied to I/O will push/pull a datum to/from a buffer, then set the associated semaphore for any task waiting on this. The semaphore is a bit-variable that set means: "ready for the task to do grab control." For input, the set semaphore means there is input data available in the buffer for the task to start using. For output, the set semaphore means that there is room in the buffer for the task to output more data. For example, for a UART input, the set semaphore means that a whole message has been sent from the desktop computer. For a UART output, the set semaphore means that there is room in the buffer for a whole message to be sent to the desktop computer. Typically an I/O buffer would be one page (256 bytes) in external memory. It is quite possible that everybody will hate Simple51 because it is non-standard, so I will be the only user. I'm okay with this. This is my usual situation! To the best of my knowledge, only two other people at Testra (John Hart and Steve Brault) ever learned how to use my assembler for the MiniForth. There has been a parade of super-duper maintenance programmers at Testra, all of whom claimed that they were better programmers than I am and that they would learn my assembler in one day and begin improving upon it --- all of them failed --- maintenance programmers aren't real programmers at all, which is why they focus on becoming experts in other people's software. _________________ When all else fails, write the source. |
|||
05 Oct 2020, 05:02 |
|
Melissa 06 Oct 2020, 01:59
Well, it is really difficult to start code from scratch these days in company
That is unless you do alone... |
|||
06 Oct 2020, 01:59 |
|
Hugh-Aguilar 07 Oct 2020, 16:00
Melissa wrote: Well, it is really difficult to start code from scratch these days in company I think your "in company" means in a job at a company, and "do alone" means a program such as my Simple51 done as a fun project at home. At Testra I had to start MFX from scratch because nobody had ever done anything like that before. The MiniForth (built on a Lattice isp1048 PLD) was a VLIW processor. Each opcode contained 5 fields, each containing an instruction. All 5 instructions would execute concurrently in a single clock cycle. My assembler allowed the programmer to write his code as if the instructions executed sequentially in the order that they appeared in his source-code. My assembler would internally rearrange the instructions so they could be packed into opcodes as much as possible. The goal was to minimize the number of NOP instructions inserted. I didn't always pack every opcode with 5 instructions, but I did get quite a lot of parallelization. Instructions with dependencies on other instructions have to execute after those other instructions, not concurrently. It is okay to read a register and write to the register concurrently though, so long as the write is after the read in the source-code. This was out-of-order execution, similar to what the Pentium was doing, except that the Pentium does the rearranging at run-time whereas my MFX assembler was doing the rearranging at compile-time. The Pentium could only parallelize two instructions (in the U and V pipe), whereas the MiniForth could parallelize five instructions, so the business of rearranging was more complicated in the MiniForth. I'm very proud of the MFX assembler. This is my best work! I'm not aware of anybody else who has done this. Tomasz Grysztar is an elite assembly-language programmer, so maybe he knows what VLIW is. What I have found though (on https://opencores.org/ for example), is that people who claim to know what VLIW is, are faking it --- they are using the term VLIW as a synonym for "super-duper" --- they don't understand how concurrent execution of instructions is possible, or what the limitations are. I did a lot in MFX to make the assembly-language programmer's job as easy as possible. The rearranging of instructions is all done internally. The assembly-language programmer can write his code as if the instructions were executed sequentially, without even knowing about the parallelization and out-of-order execution under the hood. Despite this ease, nobody has ever succeeded at learning MFX assembly-language. It is still more difficult than mainstream processors such as the MSP430. There are no jump or branch instructions other than NXT. There is no instruction for addition. The 16-bit addition is a function done with a 4-bit half-adder and XOR. This is a very low-level assembly-language that is pretty mind-blowing for anybody who is only familiar with mainstream processors such as the 6502 or 8086. Another reason why nobody has succeeded is a lack of motivation. There is no future in becoming an expert on something esoteric. Nobody outside of Testra will ever do any MFX programming, so working for Testra is a dead-end job (and the pay is low). I can make Simple51 open-source because it is pretty simple. Allowing multiple instructions per line has always been in Forth assemblers. The control-flow structures, have been in Forth assemblers since the 1980s. I was originally going to use Forth syntax, such as BEGIN WHILE REPEAT and IF ELSE THEN, but decided to invent a new syntax instead. I have curly brackets around code blocks, that look similar to C, but they are not really the same as C because the IF is inside of the code-block rather than in front of the code block. My < and > are pretty comparable to C's CONTINUE and BREAK commands. I have << <<< >> >>> that are for the parent and grandparent code-block, which C does not have. Forth doesn't have that either. Still though, this is a pretty simple assembler. BTW: I figured out how to optimize > >> and >>> (the unconditional forward jumps) to be 8-bit relative, 11-bit absolute or 16-bit long. I can allot 3 bytes to the jump. If it can be done with 8-bit relative or 11-bit absolute (two-byte instructions) then I assemble a NOP in the third byte. This bloats out the code somewhat. This does not hurt efficiency however, because the NOP never executes. This trick with the NOP was done in the 1980s in 6809 Forth assemblers --- I had just forgotten about the trick. The 8051 is 1980's technology. If the Kolibiri people steal Simple51 and put their own copyright on it (as they did with Menuet), all they have is an 8051 assembler. I can retarget it to a more modern processor such as the MSP430 and make that closed-source so they can't steal it The MSP430 is popular because it has low power-consumption. _________________ When all else fails, write the source. |
|||
07 Oct 2020, 16:00 |
|
< Last Thread | Next Thread > |
Forum Rules:
|
Copyright © 1999-2024, Tomasz Grysztar. Also on GitHub, YouTube.
Website powered by rwasa.