aboutsummaryrefslogtreecommitdiffstats
path: root/include/mips_cpu.h
blob: a0da3781bde4525e9ca5a906b845ef8c8f8bc679 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/*! \file mips_cpu.h

*/
#ifndef mips_cpu_header
#define mips_cpu_header

#include "mips_mem.h"

#ifdef __cplusplus
extern "C"{
#endif
	
/*! \defgroup mips_cpu CPU API
	\addtogroup mips_cpu
	@{
*/


/*! Represents the state of a cpu.
	
	This an opaque data type, similar to \ref mips_mem_provider.
	
	\struct mips_cpu_impl
*/
struct mips_cpu_impl;

/*! An opaque handle to a mips.

	This represents a handle to a data-type that clients can use, without
	knowing how the CPU is implemented. See \ref mips_mem_h for more
	commentary.
*/
typedef struct mips_cpu_impl *mips_cpu_h;


/*! Creates and initialises a new CPU instance.
	
	The CPU should be bound to the given
	\ref mips_mem_core "memory space", and have all registers set
	to zero. The memory is not owned by the CPU, so it should not
	be \ref mips_mem_free "freed" when the CPU is \ref mips_cpu_free "freed".
	
	\param mem The memory space the processor is connected to; think of it
	as the address bus to which the CPU has been wired.
*/
mips_cpu_h mips_cpu_create(mips_mem_h mem);

/*! Reset the CPU as if it had just been created, with all registers zerod.
	However, it should not modify RAM. Imagine this as asserting the reset
	input of the CPU core.
*/
mips_error mips_cpu_reset(mips_cpu_h state);

/*! Returns the current value of one of the 32 general purpose MIPS registers */
mips_error mips_cpu_get_register(
	mips_cpu_h state,	//!< Valid (non-empty) handle to a CPU
	unsigned index,		//!< Index from 0 to 31
	uint32_t *value		//!< Where to write the value to
);

/*! Modifies one of the 32 general purpose MIPS registers. */
mips_error mips_cpu_set_register(
	mips_cpu_h state,	//!< Valid (non-empty) handle to a CPU
	unsigned index,		//!< Index from 0 to 31
	uint32_t value		//!< New value to write into register file
);

/*! Sets the program counter for the next instruction to the specified byte address.

	While this sets the value of the PC, it should not cause any kind of
	execution to happen. Once you look at branches in detail, you will
	see that there is some slight ambiguity about this function. Choose the
	only option that makes sense.
*/
mips_error mips_cpu_set_pc(
	mips_cpu_h state,	//!< Valid (non-empty) handle to a CPU
	uint32_t pc			//!< Address of the next instruction to exectute.
);

/*! Gets the pc for the next instruction.
	
	Returns the program counter for the next instruction to be executed.
*/
mips_error mips_cpu_get_pc(
	mips_cpu_h state,	//!< Valid (non-empty) handle to a CPU
	uint32_t *pc		//!< Where to write the byte address too
);

/*! Advances the processor by one instruction.

	If an exception or error occurs, the CPU and memory state
	should be left unchanged. This is so that the user can
	inspect what happened and find out what went wrong. So
	this should be true:
	
		uint32_t pcOrig, pcGot;
		mips_cpu_get_pc(cpu, &pcOrig);
		mips_error err=mips_cpu_step(cpu);
		if(err!=mips_Success){
			mips_cpu_get_pc(cpu, &pcGot);
			assert(pcOrig==pcGot);
			assert(mips_cpu_step(cpu)==err);
	    }
	
	Maintaining this property in all cases is actually pretty
	difficult, so _try_ to maintain it, but don't worry too
	much if under some exceptions it doesn't quite work.
*/
mips_error mips_cpu_step(
	mips_cpu_h state	//! Valid (non-empty) handle to a CPU
);

/*! Controls printing of diagnostic and debug messages.

	You are encouraged to include diagnostic and debugging
	information in your CPU, but you should include a way
	to control how much is printed. The default should be
	to print nothing, but it is a good idea to have a way
	of turning it on and off _without_ recompiling. This function
	provides a way for the user to indicate both how much
	output they are interested in, and where that output
	should go (should it go to stdout, or stderr, or a file?).
	
	\param state Valid (non-empty) CPU handle.
	
	\param level What level of output should be printed. There
	is no specific format for the output format, the only
	requirement is that for level zero there is no output.
	
	\param dest Where the output should go. This should be
	remembered by the CPU simulator, then later passed
	to fprintf to provide output.
	
	\pre It is required that if level>0 then dest!=0, so the
	caller will always provide a valid destination if they
	have indicated they will require output.
	
	It is suggested that for level=1 you print out one
	line of information per instruction with basic information
	like the program counter and the instruction type, and for higher
	levels you may want to print the CPU state before each
	instruction. Both of these can usually be inserted in
	just one place in the processor, and can greatly aid
	debugging.

	However, this is completely implementation defined behaviour,
	so your simulator does not have to print anything for
	any debug level if you don't want to. 
	
	The intent is that this function merely modifies the type
	of reporting that is performed during mips_cpu_step:
	
		// Enable basic debugging to stderr
		mips_cpu_set_debug_level(cpu, 1, stderr);
		
		// The implementation may now choose to print things
		// to stderr (what exactly is up to the implementation)
		mips_cpu_step(cpu);	// e.g. prints "pc = 0x12, encoding=R"
		
		// Tell the CPU to print nothing
		mips_cpu_set_debug_level(cpu, 0, NULL);
		
		// Now the implementation must not print any debug information
		mips_cpu_step(cpu);
		
		// Send detailed debug output to a text file
		FILE *dump=fopen("dump.txt", "wt");
		mips_cpu_set_debug_level(cpu, 2, dump);
		
		// Run lots of instructions until the CPU reports an error.
		// The implementation can write lots of useful information to
		// the file
		mips_error err=mips_Success;
		while(!err) {
			mips_cpu_step(cpu);
		};
		
		// Detach the text file, and close it
		mips_cpu_set_debug_level(cpu, 0, NULL);
		fclose(dump);
		
		// You can now read through the text file "dump.txt" to see what happened

	However, you could decide that you want to print something out
	at the point that mips_cpu_set_debug_level is called with level>0,
	such as the current PC and registers. Up to you.
*/
mips_error mips_cpu_set_debug_level(mips_cpu_h state, unsigned level, FILE *dest);

/*! Free all resources associated with state.

	\param state Either a handle to a valid simulation state, or an empty (NULL) handle.

	It is legal to pass an empty (NULL) handle to mips_cpu_free. It is illegal
	to pass the same non-empty handle to mips_cpu_free twice, and will
	result in undefined behaviour (i.e. anything could happen):
	
		mips_cpu_h cpu=mips_cpu_create(...);
		...
		mips_cpu_free(h);	// This is fine
		...
		mips_cpu_free(h);	// BANG! or nothing. Or format the hard disk.
	
	A better pattern is to always zero the variable after calling free,
	in case elsewhere you are not sure if resources have been released yet:
	
		mips_cpu_h cpu=mips_cpu_create(...);
		...
		mips_cpu_free(h);	// This is fine
		h=0;	// Make sure it is now empty
		...
		mips_cpu_free(h);	// Fine, nothing happens
		h=0;    // Does nothing here, might could stop other errors
*/
void mips_cpu_free(mips_cpu_h state);

/*!
	@}
*/

#ifdef __cplusplus
};
#endif

#endif