URL
https://opencores.org/ocsvn/ion/ion/trunk
Subversion Repositories ion
[/] [ion/] [trunk/] [doc/] [src/] [tex/] [simulation.tex] - Rev 221
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.\\
