URL
https://opencores.org/ocsvn/ion/ion/trunk
Subversion Repositories ion
[/] [ion/] [trunk/] [doc/] [src/] [tex/] [simulation.tex] - Rev 250
Go to most recent revision | Compare with Previous | Blame | View Log
\chapter{Logic Simulation} \label{logic_simulation} The project has been simulated using Modelsim 6.3g. The test bench uses some features not present in earlier versions (namely library Signal Spy) so if you use some other simulator or some earlier version of Modelsim, see section on Modelsim dependencies (\ref{modelsim_dependencies}) below.\\ In short, the simulation test bench is meant to run any of the code samples provided in directory /src, under a controlled environment, while logging the cpu state to a text log file.\\ This log file can then be compared to a log file generated by a software simulator for the same code sample (see section \ref{sw_simulator}). The software simulator is the 'golden model' against which the cpu is tested, so any difference between both log files means trouble.\\ This method is far easier than building a fully automated test bench, and much more convenient and reliable than a visual inspection of the simulation state.\\ In addition to the main log file, there is a console log file to which all data written to the UART is logged (see section~\ref{uart_logging}).\\ The simulation test bench can be found in file '/vhdl/tb/mips\_tb.vhdl'. This test bench is meant to be used with all the code samples. Each of the code samples configures the simulation test bench with certain parameters (such as simulation length or memory sizes) and of course each sample has a different object code to be run. The way to pass these parameters to the simulation is through a simulation package, in file '/vhdl/tb/sim\_params\_pkg.vhdl'. This file is generated from a template whenever you 'make' each code sample (see section~\ref{samples}). The package is built using oe of the provided tools, 'build\_pkg', explained in section ~\ref{build_pkg}. Note that all code samples share the same vhdl files: you need to run the makefile target 'sim' for the sample you want to simulate; that will overwrite the package file mentioned above. So there's no vhdl file that is specific to a particular code sample.\\ While the test benches and sample code are good enough to catch MOST errors in the full system (i.e. cache included) they don't help with diagnostic; once you know there's an error, and the approximate address where it's triggered (approximate because of the cache) you have to dig into the simulation waveforms to find it.\\ \section{Running the Simulation} \label{running_the_simulation} A simulation script can be found at '/sim/mips\_tb.do'. This script will simulate the test bench entity in file '/vhdl/tb/mips\_tb.vhdl'.\\ All the code samples are run with the same script.\\ The test bench files mentioned in the previous section are automatically generated for each of the sample programs. This is automatically done by the sample code makefile, assuming you have a MIPS cross-toolchain in your computer (see section~\ref{samples}).\\ For convenience, a pre-generated file 'sim\_params\_pkg.vhdl' is included so you can launch a simulation without having to install toolchains, etc. The code is that of the 'hello world' sample.\\ I guess that if you are interested in this sort of stuff then you probably know more about Modelsim than I do. Yet, here's a step-by-step guide to simulating the 'hello world' sample: \begin{enumerate} \item Run 'make hello\_sim' from directory '/src/hello'. This will compile the program sources, build the necessary binary object files and then create the package file mentioned above.\\ Read the makefile and comments in the python script '/src/bin2hdl.py' for details.\\ ALTERNATIVELY, if you don't have a toolchain you can just skip this step and use the default vhdl files provided, which are those of the 'hello world' sample.\\ \item Within Modelsim, change directory to /syn. Modelsim will create its stuff in this directory. This includes the log file, which by default will be '/syn/hw\_sim\_log.txt', and the console log file '/syn/hw\_sim\_console.log'.\\ (You could use any other directory, this is just a convenient place to put modelsim data out of the way. Just remember where the log files are.). \item Run script '/sim/mips\_tb.do' (menu tools-\textgreater tcl-\textgreater execute macro) The simulation will run to completion and print a message in Modelsim's transcript window when it's done. You can open the console log file to see the program output, in this case the 'Hello world' message.\\ \end{enumerate} The test bench terminates the simulation when: \begin{enumerate} \item It detects two consecutive code fetches from the same address. \item The simulation timeout is reached. \end{enumerate} Condition 1 is meant to detect single-instruction loops such as those commonly found after the invocation of the main() function in a C program. This is a convenient way for the software to signal its termination.\\ The timeout is one of the simulation parameters which is defined in the makefiles. It is arbitrarily fixed for each sample by trial and error so that the program has time to execute. Change them if necessary.\\ \section{Simulation File Logging} \label{sim_logging} The simulation test bench will log any of the following events: \begin{itemize} \item Changes in the register bank. \item Changes in registers HI and LO (implemented even if mul/div is not). \item Changes in registers EPC and SR. \item Data loads (any resulting register change is logged separately). \item Data stores. \end{itemize} Note that changes in other internal registers, including PC, are not logged. This means that for example a long chain of NOPs, or MOVEs that don't change register values, will not be seen in the log file. This is on purpose.\\ Events are logged with the address of the instruction that triggered the change. This holds true even for load instructions.\\ The simulation log file is stored by default in modelsim's working directory (see above). I don't provide any automated script to do the comparison, you should use whatever diff tool you like best.\\ \section{Log File Format} \label{log_file_format} There is a text line for each of the following events: \begin{itemize} \item Register change "(pc) [reg\_num]=value"\\ Where: \begin{tabular}{ l l } pc & =\textgreater PC value (8-digit hex)\\ reg\_num & =\textgreater Register index (2-digit hex), or any of {LO,HI,EP}\\ value & =\textgreater New register value (8-digit hex)\\ \end{tabular}\\ \item Write cycle (store) "(pc) [address] |mask|=value WR"\\ Where: \begin{tabular}{ l l } pc & =\textgreater PC value (8-digit hex)\\ address & =\textgreater Write address\\ mask & =\textgreater Byte-enable mask (2-digit hex)\\ value & =\textgreater Write data\\ \end{tabular}\\ The PC value is the address of the instruction that caused the logged change, NOT the actual value of the PC at the time of the change. This is so to make the hardware logger's life easier -- the SW simulator and the real HW don't work exactly the same when the cache starts stalling the cpu (which the SW does not simulate) and the best reference point for all instructions is their own adddress. The mask will have a '1' at bits 3..0 for each byte write-enabled. MSB is bit 3, LSB is bit 0. Note that the data is big endian, so the MSB is actually the LOWER address. The upper nibble of the mask is always 0. The value will match the behavior of the ion cpu; the significant byte(s) will have the actual write data and the other bytes will not be relevant but will behave exactly as the real hardware (so that the logs are directly comparable). The WR at the end of the line is for visual reference only. \item Read cycle (load) "(pc) [address] \textless ** \textgreater =value RD"\\ Where: \begin{tabular}{ l l } pc & =\textgreater PC value (8-digit hex)\\ address & =\textgreater Read address\\ \textless ** \textgreater & =\textgreater Padding (ignore)\\ value & =\textgreater Read data\\ \end{tabular}\\ Note that in the real machine, the data is read into the cpu one cycle after the address bus is output (because the memory is synchronous) so that the full read cycle spans 2 clock cycles (when proper interlocking is implemented, the load will overlap the next instruction; right now it just stalls the pipeline for 1 cycle). This is simplified in the log files for readability. Note that the size of the read (LH/LB/LW) instruction is not recorded: the CPU always reads 32-bit words. The RD at the end of the line is for visual reference only. \end{itemize} For example, these are lines 1153-1162 of the simulation log for the default 'hello world' test program: \begin{verbatim} ... (BFC009AC) [05]=20000000 (BFC009B0) [20000020] <**>=00000003 RD (BFC009B0) [03]=00000003 (BFC009B8) [03]=00000002 (BFC009C0) [03]=20000000 (BFC009C4) [20000000] |0F|=00000070 WR (BFC00E74) [12]=00000004 (BFC00E78) [10]=BFC01048 (BFC00E7C) [BFC01048] <**>=00000069 RD (BFC00E7C) [05]=00000069 ... \end{verbatim}\\ (NOTE: this example taken from revision 176, yours may vary)\\ The read cycle at pc=0xbfc009b0 modifies register 0x03; that's why there are two lines with the same pc value.\\ The code that produced that log is this (from hello.lst): \begin{verbatim} ... bfc009ac: 3c052000 lui a1,0x2000 bfc009b0: 8ca30020 lw v1,32(a1) bfc009b4: 00000000 nop bfc009b8: 30630002 andi v1,v1,0x2 bfc009bc: 1060fffc beqz v1,0xbfc009b0 bfc009c0: 3c032000 lui v1,0x2000 bfc009c4: ac620000 sw v0,0(v1) bfc009c8: 03e00008 jr ra bfc009cc: 00000000 nop ... bfc00e74: 26520001 addiu s2,s2,1 bfc00e78: 26100001 addiu s0,s0,1 bfc00e7c: 92050000 lbu a1,0(s0) bfc00e80: 00000000 nop ... \end{verbatim}\\ (Remember the register numbers: \$v0=0x02, \$v1=0x03, \$a1=0x05, \$s0=0x10, \$s2=0x12)\\ Note that, unlike previous versions of this project, all changes are logged with the address of the instruction that caused them.\\ The log file format is hardcoded into vhdl package mips\_sim\_pkg and the software simulator C source that implement it. It will be probably modified as the project moves on so it is best if you verify all of this yourself with the project version you intend to use before using this information.\\ Note that the software simulation log and the modelsim log need not be the same size even if both CPUs behave identically; the one that spans a longer simulated time will be longer.\\ The point is that both need to be identical up to the last line of the shortest file.\\ \section{Console Output Logging} \label{uart_logging} Every byte written to the UART TX register is logged (in ascii) to a text file which by default is '/syn/hw\_sim\_console.log'. Apart from the automatic insertion of a CR after every LF, the data is logged verbatim.\\ Though the UART is included in the test bench, the actual UART operation is bypassed: The test bench forces the 'tx ready' high so that the CPU never has to wait for a character to be transmitted. This is a simplification that saves me the trouble to do a cycle-accurate simulation of the UART in the software simulator.\\ The UART input is not simulated at all, for simplicity. So, for example, the Adventure sample, which does read the console input, can't be properly simulated past the first console input -- there is plenty of code to simulate before that so this is no problem for the moment.\\ \section{Use of Modelsim Features} \label{modelsim_dependencies} Apart from the format of the simulation scripts, which would be easy to port to any other simulation tool, the simulation test bench uses a feature of Modelsim 6.3 that is not even present in earlier versions -- SignalSpy.\\ The test bench uses SignalSpy to examine internal cpu signals from the top entity, including the whole register bank. There is no other way to examine those signals in vhdl, unless you want to add them to the module interface.\\ The test bench needs to access those signals in order to detect changes in the internal cpu state that should be logged. That is, it really needs to look at those signals if it is to be of any use.\\ If you are using any other simulation tool, look for an alternative method to get those internal signals or just add them to the core interface. I would suggest adding a debug port of type record to mips\_cpu -- and hope the synthesis tool does not choke on it. Adding individual debug ports would be a PITA.\\ I guess this is why Mentor people took the trouble to write SygnalSpy.\\ I plan to move to Symphony EDA eventually, so I'll have to fix this.\\ Using GHDL would be an option, except because it only supports vhdl. The project will use a SDRAM model in verilog for which I could not find a vhdl replacement. If the project is to be ported to GHDL (a very desirable goal even if only because not everybody has access to Modelsim) this will have to be worked around.\\
Go to most recent revision | Compare with Previous | Blame | View Log