assembling the assembly language into machine code is one step completely independent of execution of machine code.
If you look at the documentation for the specific instruction set, for mips and arm and other IP vendors you go to them if the processor vendor also makes the chips like intel x86 you go there. Below I have a couple of instructions from the arm thumb instruction set,
where d,n,m are 0-7 (registers r0 through r7).
VERY VERY brute force and crude but functional:
assuming a line from a file is in a string (C code) read from the file
unsigned int gpr ( unsigned int ra, unsigned int *reg )
printf("0x%04X add r%u,r%u,r%u\n",inst,rd,rn,rm);
printf("0x%04X and r%u,r%u\n",inst,rd,rm);
VERY inflexible too, many (not all) assemblers will allow for white space.
So if I feed this simple assembler this code
and run it I get
0x18D1 add r1,r2,r3
0x1911 add r1,r2,r4
0x1802 add r2,r0,r0
0x1803 add r3,r0,r0
0x1808 add r0,r1,r0
0x19C0 add r0,r0,r7
0x4011 and r1,r2
The toolchain (compiler, assembler, linker) to be useful has a file format or set of file formats used for the source code languages plus some intermediate formats so an object file format and a final binary file format and some way to communicate the linker specs, what address space has read/write memory, maybe where if any read only (rom/flash) address space. Where computer science/convention concepts .text, .data, .bss ... are located (.text being the machine code and related binary information, .data being initialized read/write memory, think variables. and .bss being zeroed read/write memory, also think variables.
The file formats used are not relevant to the processor and how it works what the processor requires is that there is logic that can feed it instructions and data on some bus which is also not relevant, but part of the processor IMPLEMENTATION. MIPS as you have noted is used by the textbooks you mentioned to make textbook/academic style processors. Every Nth computer engineering degree major will make a mips processor in college, usually in verilog or vhdl, so there are hundreds of thousands if not another order of magnitude or two number of mips implementations out there. Most using wishbone (google it) but is again not relevant to making a mips processor.
I chose a couple of very simple ARM thumb instructions, IP that you buy from ARM and put in your chip. ARM itself has many cores and every generation is a new implementation, but somewhere in that logic a fetch happens and there is an instruction latched in the logic and some logic that in some form (pseudo code shown here)
inst_add = 0;
inst_and = 0;
rd = inst[2:0];
if(inst[15:9] == 7`b0001100)
inst_add = 1;
rn = inst[5:3];
rm = inst[8:6];
if(inst[15:6] == 10`b0100000000)
inst_and = 1;
rm = inst[5:3]
and then somewhere else the add and and implementations with flags and all that.
There is no magic to it. If you can count from 0 to 1 and understand AND, OR, NOT then you understand logic that is used to make processors, they are no more complicated than that.
You can certainly try to use discreet logic elements and draw them on paper or use schematic capture if that is still a thing or you can use an HDL, hardware design language like VHDL or Verilog (or others, yes there are others, that usually compile into VHDL or Verilog, some tools are only VHDL and Verilog is compiled into VHDL first to be used by that tool).
You can just as easily make an instruction set simulator that you can program in some programming language of your choice.
if(DISS) fprintf(stderr,"ands r%u,r%u\n",rd,rm);
Back to assemblers or language parsing in general. For various reasons a very common way to solve the problem is using bison/flex which are derived from older tools (lex,yacc)(there are other derivatives no doubt). At the end of the day the parser is this massive state machine generated by the tool For my two instruction assembler it would go character by character in the line if the first character is not an 'a' then syntax error. Then it would look at the second character and compare with n or d if neither syntax error otherwise if n then the third must be a d, the fourth a space and then into the register parsing. if the second is a d then the third a d, the fourth a space and so on. So if/when you go down this path of googling bison/flex and following a tutorial examine the c or c++ output of one of the tools and see this state machine.
There is no rule/requirement that you have to use these or any other already made parsing tools or tools that generate parsers (code that generates code like the lexx/yacc, flex/bison solutions). You can certainly come up with the parser on your own as I have done above.
while very possible to write machine code, by hand on paper or in a text editor, getting that information into a format that will then be usable by one of these tools is often not worth it, depends on the tool. gnu assembler will accept
and you could write machine code like that if you prefer. that is still assembly language being parsed by some parser and so on...
ultimately the processor and surrounding chip and memory infrastructure, the system needs to support the processors rules for booting and such, be able to feed the processor vectors and instructions and data to make it work. Somewhere in ram or rom that 0x1801 instruction lives it is then fed into the processor as the next instruction the processor picks apart the fixed portions in this case the upper 7 bits, and from that knows to parse the operand and result registers from the rest of the instruction. MIPS as you will see is not much different there is a first place you look in a mips instruction then depending on what you find there there is a secondary opcode field, and then from there the operands.
You can go to places like opencores and find a good mips or an old arm 2/3 or some others, remember you get what you pay for, there are a couple-few of good processors there, others are well...risc-v is becoming a fad for now, will see how that goes. There are some cores out there too, gnu has tools for arm, risc-v, mips and many others. as well as there being other toolchains. you can see working implementations of a processor in an HDL. some are easy to sim with verilator or icarus or ghdl or other. and from there you can use gtkwave or some other tool to see the processor in action.
Understand that the textbooks you are reading are just textbooks. An introduction to the terms, decode, execute, etc. No reason whatsoever to assume that processors buried in chips you can buy today only have a handful of pipeline steps. You can have fewer or more or no pipeline at all. The same steps have to happen fetch, decode, etc. And again if you take 100 engineers and give them an ISA and ask them to implement it you will get somewhere between 1 and 100 different implementations, assuming all are functional. More likely to be closer to the 100 than 1.
Same goes for compilers and assemblers give 100 fully capable folks a language specification and you will get somewhere between 1 and 100 different implementations that will do the job. The task is the output how you get there can vary.
Commenting on one of the other answers. Some (CISC) processors are still microcoded, it makes sense to microcode a CISC, kinda the reason RISC exists was to not be microcoded. But if you go way back and say look at visual6502, these processors were made by hand. Some folks a very well known man in particular, but anyway would draw on paper each layer of the chip and essentially hand make/draw each transistor and the metal layers that connected them. For financial and sanity reasons it made sense that we started with CISC in that era, you make a ROM based state machine, each high level CISC instruction essentially addresses into the rom and then the rom defines the sub instructions or microcode to actually implement the high level instruction. And these are not necessarily "instructions" they could be based on implementation be a bunch of discretes or mux inputs that route data to from memory busses or alus or the register file, etc. so you needed to implement the logic in a way that you could drive it from this rom, and then after you built it if there were problems you had a chance to solve it by changing the contents of the rom and not have to spin the chip at great expense esp since it was all hand drawn. It is still true today that the bulk of the cost is the layout and generating the masks essentially. Just like pcb design it is not all automated, part manual part automated.
If you are still in school there are resources where you can use I forget the term micro foundries, some colleges have equipment to make small runs of chips at a reasonable cost and/or if you take the right class at the right school is included. But you wont get the actual chip for many months just like the real world. I have not tried one of these but would like to. Am much closer to the end of my career than the college days, but some of these resources are available to the general public as well. Cheaper than spending the several to tens of millions to use a commercial fab for hire place.
short answer. The actual implementation of a compiler, assembler, linker are at the whim of the author(s) of the tools. Generally you want to have an assembler and it comes first usually as it is used to test the processor. There are sanity and other reasons why you would want your compiler to compile into assembly and not machine code, but there are exceptions to that and some that go straight to machine code which means bytes in a file format that the linker can then read and polish. And then the linker that takes objects. you dont have to have all of these there were and probably still are assemblers that output ready to run machine code already linked if you will. Then you have to get the bytes from the "binary" into rom/ram such that the processor can be fed those ones and zeros. If you look at the computer you are reading this on, there are many layers of abstraction going on. The operating system loads a binary in a file format it has defined/dictated. It probably puts it in a virtual memory space which is sanity for the programmer. All programs run from address 0x1000 in ram for example. But in reality there is a physical address buried behind that that the processor doesnt see but the memory system does. The operating system causes the processor to be in a mode where it can execute your program for a while then some other program for a while and round and round. If you get a microcontroller and program it bare metal (doesnt mean assembly language just means no operating system) or better an emulator/simulator, you can see more of these mechanics first hand.
you can create your own or start with the lc-3 instruction set definition and create your own simulator and by hand generate the machine code and feed it into that simulator. then make an assembler in whatever your favorite langauge is and so on. there is no magic to this (ignore the text books and classes, they have a microcoded implementation for the sake of microcoding, it is massive overkill, you can implement the whole lc-3 instruction set in a few dozen lines of verilog likewise in C)
Languages like java and python run on virtual machines, can be compiled down to native machine code but java in particular was not designed for that. In some other language you create the JVM a virtual machine that executes the JAVA instruction set which is what it really is. And the JAVA compiler compiles to this binary code. The virtual machine is essentially a simulator it reads the byte sees its a particular instruction task and then you implement that task in whatever language you are using to make the virtual machine. and compile/assemble that for the native processor/system you want the jvm to run on. So C++ and JAVA dont exactly take the same path to execution on the same computer, same operating system. C, C++ and other compiled languages will end up in machine code for that target processor/system.