QuizGenerator 0.7.1__py3-none-any.whl → 0.8.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- QuizGenerator/contentast.py +48 -15
- QuizGenerator/generate.py +2 -1
- QuizGenerator/mixins.py +14 -100
- QuizGenerator/premade_questions/basic.py +24 -29
- QuizGenerator/premade_questions/cst334/languages.py +100 -99
- QuizGenerator/premade_questions/cst334/math_questions.py +112 -122
- QuizGenerator/premade_questions/cst334/memory_questions.py +621 -621
- QuizGenerator/premade_questions/cst334/persistence_questions.py +137 -163
- QuizGenerator/premade_questions/cst334/process.py +312 -328
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +34 -35
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +41 -36
- QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +48 -41
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +285 -521
- QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +149 -126
- QuizGenerator/premade_questions/cst463/models/attention.py +44 -50
- QuizGenerator/premade_questions/cst463/models/cnns.py +43 -47
- QuizGenerator/premade_questions/cst463/models/matrices.py +61 -11
- QuizGenerator/premade_questions/cst463/models/rnns.py +48 -50
- QuizGenerator/premade_questions/cst463/models/text.py +65 -67
- QuizGenerator/premade_questions/cst463/models/weight_counting.py +47 -46
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +100 -156
- QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +93 -141
- QuizGenerator/question.py +310 -202
- QuizGenerator/quiz.py +8 -5
- QuizGenerator/regenerate.py +14 -6
- {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.1.dist-info}/METADATA +30 -2
- {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.1.dist-info}/RECORD +30 -30
- {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.1.dist-info}/WHEEL +0 -0
- {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.1.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.7.1.dist-info → quizgenerator-0.8.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,6 +6,7 @@ import collections
|
|
|
6
6
|
import copy
|
|
7
7
|
import enum
|
|
8
8
|
import logging
|
|
9
|
+
import random
|
|
9
10
|
import math
|
|
10
11
|
from typing import List, Optional
|
|
11
12
|
|
|
@@ -31,43 +32,45 @@ class VirtualAddressParts(MemoryQuestion, TableQuestionMixin):
|
|
|
31
32
|
VPN_BITS = "# VPN Bits"
|
|
32
33
|
OFFSET_BITS = "# Offset Bits"
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
self.Target.VPN_BITS: ca.AnswerTypes.Int(self.num_bits_vpn, unit="bits")
|
|
35
|
+
@classmethod
|
|
36
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
37
|
+
rng = random.Random(rng_seed)
|
|
38
|
+
num_bits_va = kwargs.get("num_bits_va", rng.randint(2, cls.MAX_BITS))
|
|
39
|
+
num_bits_offset = rng.randint(1, num_bits_va - 1)
|
|
40
|
+
num_bits_vpn = num_bits_va - num_bits_offset
|
|
41
|
+
|
|
42
|
+
possible_answers = {
|
|
43
|
+
cls.Target.VA_BITS: ca.AnswerTypes.Int(num_bits_va, unit="bits"),
|
|
44
|
+
cls.Target.OFFSET_BITS: ca.AnswerTypes.Int(num_bits_offset, unit="bits"),
|
|
45
|
+
cls.Target.VPN_BITS: ca.AnswerTypes.Int(num_bits_vpn, unit="bits")
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
blank_kind = rng.choice(list(cls.Target))
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
"num_bits_va": num_bits_va,
|
|
51
|
+
"num_bits_offset": num_bits_offset,
|
|
52
|
+
"num_bits_vpn": num_bits_vpn,
|
|
53
|
+
"possible_answers": possible_answers,
|
|
54
|
+
"blank_kind": blank_kind,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def _build_body(cls, context):
|
|
56
59
|
"""Build question body and collect answers."""
|
|
57
|
-
|
|
60
|
+
answer = context["possible_answers"][context["blank_kind"]]
|
|
58
61
|
|
|
59
62
|
# Create table data with one blank cell
|
|
60
63
|
table_data = [{}]
|
|
61
|
-
for target in list(
|
|
62
|
-
if target ==
|
|
64
|
+
for target in list(cls.Target):
|
|
65
|
+
if target == context["blank_kind"]:
|
|
63
66
|
# This cell should be an answer blank
|
|
64
|
-
table_data[0][target.value] =
|
|
67
|
+
table_data[0][target.value] = context["possible_answers"][target]
|
|
65
68
|
else:
|
|
66
69
|
# This cell shows the value
|
|
67
|
-
table_data[0][target.value] = f"{
|
|
70
|
+
table_data[0][target.value] = f"{context['possible_answers'][target].display} bits"
|
|
68
71
|
|
|
69
|
-
table =
|
|
70
|
-
headers=[t.value for t in list(
|
|
72
|
+
table = cls.create_fill_in_table(
|
|
73
|
+
headers=[t.value for t in list(cls.Target)],
|
|
71
74
|
template_rows=table_data
|
|
72
75
|
)
|
|
73
76
|
|
|
@@ -80,14 +83,10 @@ class VirtualAddressParts(MemoryQuestion, TableQuestionMixin):
|
|
|
80
83
|
)
|
|
81
84
|
)
|
|
82
85
|
body.add_element(table)
|
|
83
|
-
return body, answers
|
|
84
|
-
|
|
85
|
-
def get_body(self, **kwargs) -> ca.Section:
|
|
86
|
-
"""Build question body (backward compatible interface)."""
|
|
87
|
-
body, _ = self._get_body(**kwargs)
|
|
88
86
|
return body
|
|
89
87
|
|
|
90
|
-
|
|
88
|
+
@classmethod
|
|
89
|
+
def _build_explanation(cls, context):
|
|
91
90
|
"""Build question explanation."""
|
|
92
91
|
explanation = ca.Section()
|
|
93
92
|
|
|
@@ -105,20 +104,15 @@ class VirtualAddressParts(MemoryQuestion, TableQuestionMixin):
|
|
|
105
104
|
explanation.add_element(
|
|
106
105
|
ca.Paragraph(
|
|
107
106
|
[
|
|
108
|
-
ca.Text(f"{
|
|
107
|
+
ca.Text(f"{context['num_bits_va']}", emphasis=(context["blank_kind"] == cls.Target.VA_BITS)),
|
|
109
108
|
ca.Text(" = "),
|
|
110
|
-
ca.Text(f"{
|
|
109
|
+
ca.Text(f"{context['num_bits_vpn']}", emphasis=(context["blank_kind"] == cls.Target.VPN_BITS)),
|
|
111
110
|
ca.Text(" + "),
|
|
112
|
-
ca.Text(f"{
|
|
111
|
+
ca.Text(f"{context['num_bits_offset']}", emphasis=(context["blank_kind"] == cls.Target.OFFSET_BITS))
|
|
113
112
|
]
|
|
114
113
|
)
|
|
115
114
|
)
|
|
116
115
|
|
|
117
|
-
return explanation, []
|
|
118
|
-
|
|
119
|
-
def get_explanation(self, **kwargs) -> ca.Section:
|
|
120
|
-
"""Build question explanation (backward compatible interface)."""
|
|
121
|
-
explanation, _ = self._get_explanation(**kwargs)
|
|
122
116
|
return explanation
|
|
123
117
|
|
|
124
118
|
|
|
@@ -207,100 +201,116 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
|
|
|
207
201
|
|
|
208
202
|
self.hit_rate = 0. # placeholder
|
|
209
203
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
204
|
+
@classmethod
|
|
205
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
206
|
+
rng = random.Random(rng_seed)
|
|
207
|
+
num_elements = kwargs.get("num_elements", 5)
|
|
208
|
+
cache_size = kwargs.get("cache_size", 3)
|
|
209
|
+
num_requests = kwargs.get("num_requests", 10)
|
|
210
|
+
|
|
211
|
+
policy = kwargs.get("policy") or kwargs.get("algo")
|
|
212
|
+
if policy is None:
|
|
213
|
+
cache_policy = rng.choice(list(cls.Kind))
|
|
214
|
+
config_params = {"policy": cache_policy.name}
|
|
215
|
+
else:
|
|
216
|
+
if isinstance(policy, cls.Kind):
|
|
217
|
+
cache_policy = policy
|
|
218
|
+
else:
|
|
219
|
+
try:
|
|
220
|
+
cache_policy = cls.Kind[str(policy)]
|
|
221
|
+
except KeyError:
|
|
222
|
+
cache_policy = cls.Kind.FIFO
|
|
223
|
+
config_params = {"policy": cache_policy.name}
|
|
224
|
+
|
|
225
|
+
requests = (
|
|
226
|
+
list(range(cache_size))
|
|
227
|
+
+ rng.choices(population=list(range(cache_size - 1)), k=1)
|
|
228
|
+
+ rng.choices(population=list(range(cache_size, num_elements)), k=1)
|
|
229
|
+
+ rng.choices(population=list(range(num_elements)), k=(num_requests - 2))
|
|
228
230
|
)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
self.request_results = {}
|
|
231
|
+
|
|
232
|
+
cache = cls.Cache(cache_policy, cache_size, requests)
|
|
233
|
+
request_results = {}
|
|
233
234
|
number_of_hits = 0
|
|
234
|
-
for (request_number, request) in enumerate(
|
|
235
|
-
was_hit, evicted, cache_state =
|
|
236
|
-
log.debug(f"cache_state: \"{cache_state}\"")
|
|
235
|
+
for (request_number, request) in enumerate(requests):
|
|
236
|
+
was_hit, evicted, cache_state = cache.query_cache(request, request_number)
|
|
237
237
|
if was_hit:
|
|
238
238
|
number_of_hits += 1
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
self.hit_rate = 100 * number_of_hits / (self.num_requests)
|
|
255
|
-
self.answers.update(
|
|
256
|
-
{
|
|
257
|
-
"answer__hit_rate": ca.AnswerTypes.Float(self.hit_rate,
|
|
258
|
-
label=f"Hit rate, excluding non-capacity misses",
|
|
259
|
-
unit="%"
|
|
260
|
-
)
|
|
239
|
+
hit_value = 'hit' if was_hit else 'miss'
|
|
240
|
+
evicted_value = '-' if evicted is None else f"{evicted}"
|
|
241
|
+
cache_state_value = copy.copy(cache_state)
|
|
242
|
+
|
|
243
|
+
request_results[request_number] = {
|
|
244
|
+
"request": request,
|
|
245
|
+
"hit_value": hit_value,
|
|
246
|
+
"evicted_value": evicted_value,
|
|
247
|
+
"cache_state_value": cache_state_value,
|
|
248
|
+
"hit_answer": ca.AnswerTypes.String(hit_value),
|
|
249
|
+
"evicted_answer": ca.AnswerTypes.String(evicted_value),
|
|
250
|
+
"cache_state_answer": ca.AnswerTypes.List(
|
|
251
|
+
value=cache_state_value,
|
|
252
|
+
order_matters=True
|
|
253
|
+
),
|
|
261
254
|
}
|
|
255
|
+
|
|
256
|
+
hit_rate = 100 * number_of_hits / num_requests
|
|
257
|
+
hit_rate_answer = ca.AnswerTypes.Float(
|
|
258
|
+
hit_rate,
|
|
259
|
+
label="Hit rate, excluding non-capacity misses",
|
|
260
|
+
unit="%"
|
|
262
261
|
)
|
|
263
262
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
263
|
+
return {
|
|
264
|
+
"num_elements": num_elements,
|
|
265
|
+
"cache_size": cache_size,
|
|
266
|
+
"num_requests": num_requests,
|
|
267
|
+
"cache_policy": cache_policy,
|
|
268
|
+
"requests": requests,
|
|
269
|
+
"request_results": request_results,
|
|
270
|
+
"hit_rate": hit_rate,
|
|
271
|
+
"hit_rate_answer": hit_rate_answer,
|
|
272
|
+
"_config_params": config_params,
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@classmethod
|
|
276
|
+
def is_interesting_ctx(cls, context) -> bool:
|
|
277
|
+
return (context["hit_rate"] / 100.0) < 0.7
|
|
278
|
+
|
|
279
|
+
@classmethod
|
|
280
|
+
def _build_body(cls, context):
|
|
268
281
|
"""Build question body and collect answers."""
|
|
269
282
|
answers = []
|
|
270
283
|
|
|
271
284
|
# Create table data for cache simulation
|
|
272
285
|
table_rows = []
|
|
273
|
-
for request_number in sorted(
|
|
286
|
+
for request_number in sorted(context["request_results"].keys()):
|
|
287
|
+
result = context["request_results"][request_number]
|
|
274
288
|
table_rows.append(
|
|
275
289
|
{
|
|
276
|
-
"Page Requested": f"{
|
|
277
|
-
"Hit/Miss":
|
|
278
|
-
"Evicted":
|
|
279
|
-
"Cache State":
|
|
290
|
+
"Page Requested": f"{context['requests'][request_number]}",
|
|
291
|
+
"Hit/Miss": result["hit_answer"],
|
|
292
|
+
"Evicted": result["evicted_answer"],
|
|
293
|
+
"Cache State": result["cache_state_answer"]
|
|
280
294
|
}
|
|
281
295
|
)
|
|
282
296
|
# Collect answers for this request
|
|
283
|
-
answers.append(
|
|
284
|
-
answers.append(
|
|
285
|
-
answers.append(
|
|
297
|
+
answers.append(result["hit_answer"])
|
|
298
|
+
answers.append(result["evicted_answer"])
|
|
299
|
+
answers.append(result["cache_state_answer"])
|
|
286
300
|
|
|
287
301
|
# Create table using mixin - automatically handles answer conversion
|
|
288
|
-
cache_table =
|
|
302
|
+
cache_table = cls.create_answer_table(
|
|
289
303
|
headers=["Page Requested", "Hit/Miss", "Evicted", "Cache State"],
|
|
290
304
|
data_rows=table_rows,
|
|
291
305
|
answer_columns=["Hit/Miss", "Evicted", "Cache State"]
|
|
292
306
|
)
|
|
293
307
|
|
|
294
|
-
# Collect hit rate answer
|
|
295
|
-
hit_rate_answer = self.answers["answer__hit_rate"]
|
|
296
|
-
answers.append(hit_rate_answer)
|
|
297
|
-
|
|
298
308
|
# Create hit rate answer block
|
|
299
|
-
hit_rate_block = ca.AnswerBlock(hit_rate_answer)
|
|
309
|
+
hit_rate_block = ca.AnswerBlock(context["hit_rate_answer"])
|
|
300
310
|
|
|
301
311
|
# Use mixin to create complete body
|
|
302
312
|
intro_text = (
|
|
303
|
-
f"Assume we are using a **{
|
|
313
|
+
f"Assume we are using a **{context['cache_policy']}** caching policy and a cache size of **{context['cache_size']}**. "
|
|
304
314
|
"Given the below series of requests please fill in the table. "
|
|
305
315
|
"For the hit/miss column, please write either \"hit\" or \"miss\". "
|
|
306
316
|
"For the eviction column, please write either the number of the evicted page or simply a dash (e.g. \"-\")."
|
|
@@ -313,16 +323,12 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
|
|
|
313
323
|
"In the case where there is a tie, order by increasing number."
|
|
314
324
|
])
|
|
315
325
|
|
|
316
|
-
body =
|
|
326
|
+
body = cls.create_fill_in_table_body(intro_text, instructions, cache_table)
|
|
317
327
|
body.add_element(hit_rate_block)
|
|
318
|
-
return body, answers
|
|
319
|
-
|
|
320
|
-
def get_body(self, **kwargs) -> ca.Section:
|
|
321
|
-
"""Build question body (backward compatible interface)."""
|
|
322
|
-
body, _ = self._get_body(**kwargs)
|
|
323
328
|
return body
|
|
324
329
|
|
|
325
|
-
|
|
330
|
+
@classmethod
|
|
331
|
+
def _build_explanation(cls, context):
|
|
326
332
|
"""Build question explanation."""
|
|
327
333
|
explanation = ca.Section()
|
|
328
334
|
|
|
@@ -333,12 +339,12 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
|
|
|
333
339
|
headers=["Page", "Hit/Miss", "Evicted", "Cache State"],
|
|
334
340
|
data=[
|
|
335
341
|
[
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
f'{
|
|
339
|
-
f'{
|
|
342
|
+
context["request_results"][request]["request"],
|
|
343
|
+
context["request_results"][request]["hit_value"],
|
|
344
|
+
f'{context["request_results"][request]["evicted_value"]}',
|
|
345
|
+
f'{",".join(map(str, context["request_results"][request]["cache_state_value"]))}',
|
|
340
346
|
]
|
|
341
|
-
for (request_number, request) in enumerate(sorted(
|
|
347
|
+
for (request_number, request) in enumerate(sorted(context["request_results"].keys()))
|
|
342
348
|
]
|
|
343
349
|
)
|
|
344
350
|
)
|
|
@@ -348,23 +354,13 @@ class CachingQuestion(MemoryQuestion, RegenerableChoiceMixin, TableQuestionMixin
|
|
|
348
354
|
[
|
|
349
355
|
"To calculate the hit rate we calculate the percentage of requests "
|
|
350
356
|
"that were cache hits out of the total number of requests. "
|
|
351
|
-
f"In this case we are counting only all but {
|
|
357
|
+
f"In this case we are counting only all but {context['cache_size']} requests, "
|
|
352
358
|
f"since we are excluding capacity misses."
|
|
353
359
|
]
|
|
354
360
|
)
|
|
355
361
|
)
|
|
356
362
|
|
|
357
|
-
return explanation, []
|
|
358
|
-
|
|
359
|
-
def get_explanation(self, **kwargs) -> ca.Section:
|
|
360
|
-
"""Build question explanation (backward compatible interface)."""
|
|
361
|
-
explanation, _ = self._get_explanation(**kwargs)
|
|
362
363
|
return explanation
|
|
363
|
-
|
|
364
|
-
def is_interesting(self) -> bool:
|
|
365
|
-
# todo: interesting is more likely based on whether I can differentiate between it and another algo,
|
|
366
|
-
# so maybe rerun with a different approach but same requests?
|
|
367
|
-
return (self.hit_rate / 100.0) < 0.7
|
|
368
364
|
|
|
369
365
|
|
|
370
366
|
class MemoryAccessQuestion(MemoryQuestion, abc.ABC):
|
|
@@ -377,48 +373,51 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
|
|
|
377
373
|
MIN_BOUNDS_BIT = 5
|
|
378
374
|
MAX_BOUNDS_BITS = 16
|
|
379
375
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
self.MIN_BOUNDS_BIT,
|
|
387
|
-
self.MAX_BOUNDS_BITS
|
|
376
|
+
@classmethod
|
|
377
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
378
|
+
rng = random.Random(rng_seed)
|
|
379
|
+
bounds_bits = rng.randint(
|
|
380
|
+
cls.MIN_BOUNDS_BIT,
|
|
381
|
+
cls.MAX_BOUNDS_BITS
|
|
388
382
|
)
|
|
389
|
-
base_bits =
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
383
|
+
base_bits = cls.MAX_BITS - bounds_bits
|
|
384
|
+
|
|
385
|
+
bounds = int(math.pow(2, bounds_bits))
|
|
386
|
+
base = rng.randint(1, int(math.pow(2, base_bits))) * bounds
|
|
387
|
+
virtual_address = rng.randint(1, int(bounds / cls.PROBABILITY_OF_VALID))
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
"bounds": bounds,
|
|
391
|
+
"base": base,
|
|
392
|
+
"virtual_address": virtual_address,
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
@classmethod
|
|
396
|
+
def _build_body(cls, context):
|
|
397
|
+
"""Build question body and collect answers."""
|
|
398
|
+
if context["virtual_address"] < context["bounds"]:
|
|
399
|
+
answer = ca.AnswerTypes.Hex(
|
|
400
|
+
context["base"] + context["virtual_address"],
|
|
401
|
+
length=math.ceil(math.log2(context["base"] + context["virtual_address"]))
|
|
399
402
|
)
|
|
400
403
|
else:
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
def _get_body(self):
|
|
404
|
-
"""Build question body and collect answers."""
|
|
405
|
-
answers = [self.answers["answer"]]
|
|
404
|
+
answer = ca.AnswerTypes.String("INVALID")
|
|
406
405
|
|
|
407
406
|
# Use mixin to create parameter table with answer
|
|
408
407
|
parameter_info = {
|
|
409
|
-
"Base": f"0x{
|
|
410
|
-
"Bounds": f"0x{
|
|
411
|
-
"Virtual Address": f"0x{
|
|
408
|
+
"Base": f"0x{context['base']:X}",
|
|
409
|
+
"Bounds": f"0x{context['bounds']:X}",
|
|
410
|
+
"Virtual Address": f"0x{context['virtual_address']:X}"
|
|
412
411
|
}
|
|
413
412
|
|
|
414
|
-
table =
|
|
413
|
+
table = cls.create_parameter_answer_table(
|
|
415
414
|
parameter_info=parameter_info,
|
|
416
415
|
answer_label="Physical Address",
|
|
417
|
-
|
|
416
|
+
answer=answer,
|
|
418
417
|
transpose=True
|
|
419
418
|
)
|
|
420
419
|
|
|
421
|
-
body =
|
|
420
|
+
body = cls.create_parameter_calculation_body(
|
|
422
421
|
intro_text=(
|
|
423
422
|
"Given the information in the below table, "
|
|
424
423
|
"please calcuate the physical address associated with the given virtual address. "
|
|
@@ -426,14 +425,10 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
|
|
|
426
425
|
),
|
|
427
426
|
parameter_table=table
|
|
428
427
|
)
|
|
429
|
-
return body, answers
|
|
430
|
-
|
|
431
|
-
def get_body(self) -> ca.Section:
|
|
432
|
-
"""Build question body (backward compatible interface)."""
|
|
433
|
-
body, _ = self._get_body()
|
|
434
428
|
return body
|
|
435
429
|
|
|
436
|
-
|
|
430
|
+
@classmethod
|
|
431
|
+
def _build_explanation(cls, context):
|
|
437
432
|
"""Build question explanation."""
|
|
438
433
|
explanation = ca.Section()
|
|
439
434
|
|
|
@@ -451,19 +446,19 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
|
|
|
451
446
|
explanation.add_element(
|
|
452
447
|
ca.Paragraph(
|
|
453
448
|
[
|
|
454
|
-
f"Step 1: 0x{
|
|
455
|
-
f"--> {'***VALID***' if (
|
|
449
|
+
f"Step 1: 0x{context['virtual_address']:X} < 0x{context['bounds']:X} "
|
|
450
|
+
f"--> {'***VALID***' if (context['virtual_address'] < context['bounds']) else 'INVALID'}"
|
|
456
451
|
]
|
|
457
452
|
)
|
|
458
453
|
)
|
|
459
454
|
|
|
460
|
-
if
|
|
455
|
+
if context["virtual_address"] < context["bounds"]:
|
|
461
456
|
explanation.add_element(
|
|
462
457
|
ca.Paragraph(
|
|
463
458
|
[
|
|
464
459
|
f"Step 2: Since the previous check passed, we calculate "
|
|
465
|
-
f"0x{
|
|
466
|
-
f"= ***0x{
|
|
460
|
+
f"0x{context['base']:X} + 0x{context['virtual_address']:X} "
|
|
461
|
+
f"= ***0x{context['base'] + context['virtual_address']:X}***.",
|
|
467
462
|
"If it had been invalid we would have simply written INVALID"
|
|
468
463
|
]
|
|
469
464
|
)
|
|
@@ -474,17 +469,12 @@ class BaseAndBounds(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin
|
|
|
474
469
|
[
|
|
475
470
|
f"Step 2: Since the previous check failed, we simply write ***INVALID***.",
|
|
476
471
|
"***If*** it had been valid, we would have calculated "
|
|
477
|
-
f"0x{
|
|
478
|
-
f"= 0x{
|
|
472
|
+
f"0x{context['base']:X} + 0x{context['virtual_address']:X} "
|
|
473
|
+
f"= 0x{context['base'] + context['virtual_address']:X}.",
|
|
479
474
|
]
|
|
480
475
|
)
|
|
481
476
|
)
|
|
482
477
|
|
|
483
|
-
return explanation, []
|
|
484
|
-
|
|
485
|
-
def get_explanation(self) -> ca.Section:
|
|
486
|
-
"""Build question explanation (backward compatible interface)."""
|
|
487
|
-
explanation, _ = self._get_explanation()
|
|
488
478
|
return explanation
|
|
489
479
|
|
|
490
480
|
|
|
@@ -494,7 +484,8 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
|
|
|
494
484
|
MIN_VIRTUAL_BITS = 5
|
|
495
485
|
MAX_VIRTUAL_BITS = 10
|
|
496
486
|
|
|
497
|
-
|
|
487
|
+
@staticmethod
|
|
488
|
+
def _within_bounds(segment, offset, bounds):
|
|
498
489
|
if segment == "unallocated":
|
|
499
490
|
return False
|
|
500
491
|
elif bounds < offset:
|
|
@@ -502,28 +493,29 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
|
|
|
502
493
|
else:
|
|
503
494
|
return True
|
|
504
495
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
496
|
+
@classmethod
|
|
497
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
498
|
+
rng = random.Random(rng_seed)
|
|
499
|
+
|
|
508
500
|
# Pick how big each of our address spaces will be
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
501
|
+
virtual_bits = rng.randint(cls.MIN_VIRTUAL_BITS, cls.MAX_VIRTUAL_BITS)
|
|
502
|
+
physical_bits = rng.randint(virtual_bits + 1, cls.MAX_BITS)
|
|
503
|
+
|
|
512
504
|
# Start with blank base and bounds
|
|
513
|
-
|
|
505
|
+
base = {
|
|
514
506
|
"code": 0,
|
|
515
507
|
"heap": 0,
|
|
516
508
|
"stack": 0,
|
|
517
509
|
}
|
|
518
|
-
|
|
510
|
+
bounds = {
|
|
519
511
|
"code": 0,
|
|
520
512
|
"heap": 0,
|
|
521
513
|
"stack": 0,
|
|
522
514
|
}
|
|
523
|
-
|
|
515
|
+
|
|
524
516
|
min_bounds = 4
|
|
525
|
-
max_bounds = int(2 ** (
|
|
526
|
-
|
|
517
|
+
max_bounds = int(2 ** (virtual_bits - 2))
|
|
518
|
+
|
|
527
519
|
def segment_collision(base, bounds):
|
|
528
520
|
# lol, I think this is probably silly, but should work
|
|
529
521
|
return 0 != len(
|
|
@@ -534,77 +526,81 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
|
|
|
534
526
|
]
|
|
535
527
|
)
|
|
536
528
|
)
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
529
|
+
|
|
530
|
+
base["unallocated"] = 0
|
|
531
|
+
bounds["unallocated"] = 0
|
|
532
|
+
|
|
541
533
|
# Make random placements and check to make sure they are not overlapping
|
|
542
|
-
while (segment_collision(
|
|
543
|
-
for segment in
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
534
|
+
while (segment_collision(base, bounds)):
|
|
535
|
+
for segment in base.keys():
|
|
536
|
+
bounds[segment] = rng.randint(min_bounds, max_bounds - 1)
|
|
537
|
+
base[segment] = rng.randint(0, (2 ** physical_bits - bounds[segment]))
|
|
538
|
+
|
|
547
539
|
# Pick a random segment for us to use
|
|
548
|
-
|
|
549
|
-
|
|
540
|
+
segment = rng.choice(list(base.keys()))
|
|
541
|
+
segment_bits = {
|
|
550
542
|
"code": 0,
|
|
551
543
|
"heap": 1,
|
|
552
544
|
"unallocated": 2,
|
|
553
545
|
"stack": 3
|
|
554
|
-
}[
|
|
555
|
-
|
|
546
|
+
}[segment]
|
|
547
|
+
|
|
556
548
|
# Try to pick a random address within that range
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
)
|
|
549
|
+
if segment == "unallocated":
|
|
550
|
+
offset = rng.randint(0, max_bounds - 1)
|
|
551
|
+
else:
|
|
552
|
+
max_offset = min(
|
|
553
|
+
[
|
|
554
|
+
max_bounds - 1,
|
|
555
|
+
max(1, int(bounds[segment] / cls.PROBABILITY_OF_VALID))
|
|
556
|
+
]
|
|
566
557
|
)
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
self.offset = self.rng.randint(0, max_bounds - 1)
|
|
570
|
-
|
|
558
|
+
offset = rng.randint(1, max_offset)
|
|
559
|
+
|
|
571
560
|
# Calculate a virtual address based on the segment and the offset
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
561
|
+
virtual_address = (
|
|
562
|
+
(segment_bits << (virtual_bits - 2))
|
|
563
|
+
+ offset
|
|
575
564
|
)
|
|
576
|
-
|
|
565
|
+
|
|
577
566
|
# Calculate physical address based on offset
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
567
|
+
physical_address = base[segment] + offset
|
|
568
|
+
|
|
569
|
+
return {
|
|
570
|
+
"virtual_bits": virtual_bits,
|
|
571
|
+
"physical_bits": physical_bits,
|
|
572
|
+
"base": base,
|
|
573
|
+
"bounds": bounds,
|
|
574
|
+
"segment": segment,
|
|
575
|
+
"segment_bits": segment_bits,
|
|
576
|
+
"offset": offset,
|
|
577
|
+
"virtual_address": virtual_address,
|
|
578
|
+
"physical_address": physical_address,
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
@classmethod
|
|
582
|
+
def _build_body(cls, context):
|
|
583
|
+
"""Build question body and collect answers."""
|
|
584
|
+
segment_answer = ca.AnswerTypes.String(context["segment"], label="Segment name")
|
|
585
|
+
if cls._within_bounds(context["segment"], context["offset"], context["bounds"][context["segment"]]):
|
|
586
|
+
physical_answer = ca.AnswerTypes.Binary(
|
|
587
|
+
context["physical_address"],
|
|
588
|
+
length=context["physical_bits"],
|
|
585
589
|
label="Physical Address"
|
|
586
590
|
)
|
|
587
591
|
else:
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
self.answers["answer__segment"] = ca.AnswerTypes.String(self.segment, label="Segment name")
|
|
591
|
-
|
|
592
|
-
def _get_body(self):
|
|
593
|
-
"""Build question body and collect answers."""
|
|
594
|
-
answers = [
|
|
595
|
-
self.answers["answer__segment"],
|
|
596
|
-
self.answers["answer__physical_address"]
|
|
597
|
-
]
|
|
592
|
+
physical_answer = ca.AnswerTypes.String("INVALID", label="Physical Address")
|
|
593
|
+
answers = [segment_answer, physical_answer]
|
|
598
594
|
|
|
599
595
|
body = ca.Section()
|
|
600
596
|
|
|
601
597
|
body.add_element(
|
|
602
598
|
ca.Paragraph(
|
|
603
599
|
[
|
|
604
|
-
f"Given a virtual address space of {
|
|
605
|
-
f"and a physical address space of {
|
|
600
|
+
f"Given a virtual address space of {context['virtual_bits']}bits, "
|
|
601
|
+
f"and a physical address space of {context['physical_bits']}bits, "
|
|
606
602
|
"what is the physical address associated with the virtual address "
|
|
607
|
-
f"0b{
|
|
603
|
+
f"0b{context['virtual_address']:0{context['virtual_bits']}b}?",
|
|
608
604
|
"If it is invalid simply type INVALID.",
|
|
609
605
|
"Note: assume that the stack grows in the same way as the code and the heap."
|
|
610
606
|
]
|
|
@@ -613,12 +609,24 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
|
|
|
613
609
|
|
|
614
610
|
# Create segment table using mixin
|
|
615
611
|
segment_rows = [
|
|
616
|
-
{
|
|
617
|
-
|
|
618
|
-
|
|
612
|
+
{
|
|
613
|
+
"": "code",
|
|
614
|
+
"base": f"0b{context['base']['code']:0{context['physical_bits']}b}",
|
|
615
|
+
"bounds": f"0b{context['bounds']['code']:0b}"
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
"": "heap",
|
|
619
|
+
"base": f"0b{context['base']['heap']:0{context['physical_bits']}b}",
|
|
620
|
+
"bounds": f"0b{context['bounds']['heap']:0b}"
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
"": "stack",
|
|
624
|
+
"base": f"0b{context['base']['stack']:0{context['physical_bits']}b}",
|
|
625
|
+
"bounds": f"0b{context['bounds']['stack']:0b}"
|
|
626
|
+
}
|
|
619
627
|
]
|
|
620
628
|
|
|
621
|
-
segment_table =
|
|
629
|
+
segment_table = cls.create_answer_table(
|
|
622
630
|
headers=["", "base", "bounds"],
|
|
623
631
|
data_rows=segment_rows,
|
|
624
632
|
answer_columns=[] # No answer columns in this table
|
|
@@ -628,18 +636,14 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
|
|
|
628
636
|
|
|
629
637
|
body.add_element(
|
|
630
638
|
ca.AnswerBlock([
|
|
631
|
-
|
|
632
|
-
|
|
639
|
+
segment_answer,
|
|
640
|
+
physical_answer
|
|
633
641
|
])
|
|
634
642
|
)
|
|
635
643
|
return body, answers
|
|
636
644
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
body, _ = self._get_body()
|
|
640
|
-
return body
|
|
641
|
-
|
|
642
|
-
def _get_explanation(self):
|
|
645
|
+
@classmethod
|
|
646
|
+
def _build_explanation(cls, context):
|
|
643
647
|
explanation = ca.Section()
|
|
644
648
|
|
|
645
649
|
explanation.add_element(
|
|
@@ -657,15 +661,15 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
|
|
|
657
661
|
ca.Paragraph(
|
|
658
662
|
[
|
|
659
663
|
f"In this problem our virtual address, "
|
|
660
|
-
f"converted to binary and including padding, is 0b{
|
|
661
|
-
f"From this we know that our segment bits are 0b{
|
|
662
|
-
f"meaning that we are in the ***{
|
|
664
|
+
f"converted to binary and including padding, is 0b{context['virtual_address']:0{context['virtual_bits']}b}.",
|
|
665
|
+
f"From this we know that our segment bits are 0b{context['segment_bits']:02b}, "
|
|
666
|
+
f"meaning that we are in the ***{context['segment']}*** segment.",
|
|
663
667
|
""
|
|
664
668
|
]
|
|
665
669
|
)
|
|
666
670
|
)
|
|
667
671
|
|
|
668
|
-
if
|
|
672
|
+
if context["segment"] == "unallocated":
|
|
669
673
|
explanation.add_element(
|
|
670
674
|
ca.Paragraph(
|
|
671
675
|
[
|
|
@@ -677,17 +681,17 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
|
|
|
677
681
|
explanation.add_element(
|
|
678
682
|
ca.Paragraph(
|
|
679
683
|
[
|
|
680
|
-
f"Since we are in the {
|
|
681
|
-
f"we see from our table that our bounds are {
|
|
682
|
-
f"Remember that our check for our {
|
|
683
|
-
f"`if (offset >= bounds({
|
|
684
|
+
f"Since we are in the {context['segment']} segment, "
|
|
685
|
+
f"we see from our table that our bounds are {context['bounds'][context['segment']]}. "
|
|
686
|
+
f"Remember that our check for our {context['segment']} segment is: ",
|
|
687
|
+
f"`if (offset >= bounds({context['segment']})) : INVALID`",
|
|
684
688
|
"which becomes"
|
|
685
|
-
f"`if ({
|
|
689
|
+
f"`if ({context['offset']:0b} > {context['bounds'][context['segment']]:0b}) : INVALID`"
|
|
686
690
|
]
|
|
687
691
|
)
|
|
688
692
|
)
|
|
689
693
|
|
|
690
|
-
if not
|
|
694
|
+
if not cls._within_bounds(context["segment"], context["offset"], context["bounds"][context["segment"]]):
|
|
691
695
|
# then we are outside of bounds
|
|
692
696
|
explanation.add_element(
|
|
693
697
|
ca.Paragraph(
|
|
@@ -713,7 +717,7 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
|
|
|
713
717
|
"To find the physical address we use the formula:",
|
|
714
718
|
"<code>physical_address = base(segment) + offset</code>",
|
|
715
719
|
"which becomes",
|
|
716
|
-
f"<code>physical_address = {
|
|
720
|
+
f"<code>physical_address = {context['base'][context['segment']]:0b} + {context['offset']:0b}</code>.",
|
|
717
721
|
""
|
|
718
722
|
]
|
|
719
723
|
)
|
|
@@ -728,19 +732,14 @@ class Segmentation(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin)
|
|
|
728
732
|
)
|
|
729
733
|
explanation.add_element(
|
|
730
734
|
ca.Code(
|
|
731
|
-
f" 0b{
|
|
732
|
-
f"<u>+ 0b{
|
|
733
|
-
f" 0b{
|
|
735
|
+
f" 0b{context['base'][context['segment']]:0{context['physical_bits']}b}\n"
|
|
736
|
+
f"<u>+ 0b{context['offset']:0{context['physical_bits']}b}</u>\n"
|
|
737
|
+
f" 0b{context['physical_address']:0{context['physical_bits']}b}\n"
|
|
734
738
|
)
|
|
735
739
|
)
|
|
736
740
|
|
|
737
741
|
return explanation, []
|
|
738
742
|
|
|
739
|
-
def get_explanation(self) -> ca.Section:
|
|
740
|
-
"""Build question explanation (backward compatible interface)."""
|
|
741
|
-
explanation, _ = self._get_explanation()
|
|
742
|
-
return explanation
|
|
743
|
-
|
|
744
743
|
|
|
745
744
|
@QuestionRegistry.register()
|
|
746
745
|
class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
@@ -752,99 +751,100 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
752
751
|
MAX_VPN_BITS = 8
|
|
753
752
|
MAX_PFN_BITS = 16
|
|
754
753
|
|
|
755
|
-
|
|
756
|
-
|
|
754
|
+
@classmethod
|
|
755
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
756
|
+
rng = random.Random(rng_seed)
|
|
757
757
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
758
|
+
num_bits_offset = rng.randint(cls.MIN_OFFSET_BITS, cls.MAX_OFFSET_BITS)
|
|
759
|
+
num_bits_vpn = rng.randint(cls.MIN_VPN_BITS, cls.MAX_VPN_BITS)
|
|
760
|
+
num_bits_pfn = rng.randint(max([cls.MIN_PFN_BITS, num_bits_vpn]), cls.MAX_PFN_BITS)
|
|
761
761
|
|
|
762
|
-
|
|
762
|
+
virtual_address = rng.randint(1, 2 ** (num_bits_vpn + num_bits_offset))
|
|
763
763
|
|
|
764
764
|
# Calculate these two
|
|
765
|
-
|
|
766
|
-
|
|
765
|
+
offset = virtual_address % (2 ** (num_bits_offset))
|
|
766
|
+
vpn = virtual_address // (2 ** (num_bits_offset))
|
|
767
767
|
|
|
768
768
|
# Generate this randomly
|
|
769
|
-
|
|
769
|
+
pfn = rng.randint(0, 2 ** (num_bits_pfn))
|
|
770
770
|
|
|
771
771
|
# Calculate this
|
|
772
|
-
|
|
772
|
+
physical_address = pfn * (2 ** num_bits_offset) + offset
|
|
773
773
|
|
|
774
|
-
if
|
|
775
|
-
|
|
774
|
+
if rng.choices([True, False], weights=[(cls.PROBABILITY_OF_VALID), (1 - cls.PROBABILITY_OF_VALID)], k=1)[0]:
|
|
775
|
+
is_valid = True
|
|
776
776
|
# Set our actual entry to be in the table and valid
|
|
777
|
-
|
|
778
|
-
# self.physical_address_var = VariableHex("Physical Address", self.physical_address, num_bits=(self.num_pfn_bits+self.num_offset_bits), default_presentation=VariableHex.PRESENTATION.BINARY)
|
|
779
|
-
# self.pfn_var = VariableHex("PFN", self.pfn, num_bits=self.num_pfn_bits, default_presentation=VariableHex.PRESENTATION.BINARY)
|
|
777
|
+
pte = pfn + (2 ** (num_bits_pfn))
|
|
780
778
|
else:
|
|
781
|
-
|
|
779
|
+
is_valid = False
|
|
782
780
|
# Leave it as invalid
|
|
783
|
-
|
|
784
|
-
# self.physical_address_var = Variable("Physical Address", "INVALID")
|
|
785
|
-
# self.pfn_var = Variable("PFN", "INVALID")
|
|
786
|
-
|
|
787
|
-
# self.pte_var = VariableHex("PTE", self.pte, num_bits=(self.num_pfn_bits+1), default_presentation=VariableHex.PRESENTATION.BINARY)
|
|
781
|
+
pte = pfn
|
|
788
782
|
|
|
789
783
|
# Generate page table (moved from get_body to ensure deterministic generation)
|
|
790
|
-
table_size =
|
|
784
|
+
table_size = rng.randint(5, 8)
|
|
791
785
|
|
|
792
|
-
lowest_possible_bottom = max([0,
|
|
793
|
-
highest_possible_bottom = min([2 **
|
|
786
|
+
lowest_possible_bottom = max([0, vpn - table_size])
|
|
787
|
+
highest_possible_bottom = min([2 ** num_bits_vpn - table_size, vpn])
|
|
794
788
|
|
|
795
|
-
table_bottom =
|
|
789
|
+
table_bottom = rng.randint(lowest_possible_bottom, highest_possible_bottom)
|
|
796
790
|
table_top = table_bottom + table_size
|
|
797
791
|
|
|
798
|
-
|
|
799
|
-
|
|
792
|
+
page_table = {}
|
|
793
|
+
page_table[vpn] = pte
|
|
800
794
|
|
|
801
795
|
# Fill in the rest of the table
|
|
802
|
-
for
|
|
803
|
-
if
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
796
|
+
for vpn_idx in range(table_bottom, table_top):
|
|
797
|
+
if vpn_idx == vpn:
|
|
798
|
+
continue
|
|
799
|
+
pte_candidate = page_table[vpn]
|
|
800
|
+
while pte_candidate in page_table.values():
|
|
801
|
+
pte_candidate = rng.randint(0, 2 ** num_bits_pfn - 1)
|
|
802
|
+
if rng.choices([True, False], weights=[(1 - cls.PROBABILITY_OF_VALID), cls.PROBABILITY_OF_VALID], k=1)[0]:
|
|
808
803
|
# Randomly set it to be valid
|
|
809
|
-
|
|
804
|
+
pte_candidate += (2 ** num_bits_pfn)
|
|
810
805
|
# Once we have a unique random entry, put it into the Page Table
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
806
|
+
page_table[vpn_idx] = pte_candidate
|
|
807
|
+
|
|
808
|
+
return {
|
|
809
|
+
'num_bits_offset': num_bits_offset,
|
|
810
|
+
'num_bits_vpn': num_bits_vpn,
|
|
811
|
+
'num_bits_pfn': num_bits_pfn,
|
|
812
|
+
'virtual_address': virtual_address,
|
|
813
|
+
'offset': offset,
|
|
814
|
+
'vpn': vpn,
|
|
815
|
+
'pfn': pfn,
|
|
816
|
+
'physical_address': physical_address,
|
|
817
|
+
'is_valid': is_valid,
|
|
818
|
+
'pte': pte,
|
|
819
|
+
'page_table': page_table,
|
|
820
|
+
}
|
|
820
821
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
822
|
+
@classmethod
|
|
823
|
+
def _build_body(cls, context):
|
|
824
|
+
"""Build question body and collect answers."""
|
|
825
|
+
vpn_answer = ca.AnswerTypes.Binary(context['vpn'], length=context['num_bits_vpn'], label='VPN')
|
|
826
|
+
offset_answer = ca.AnswerTypes.Binary(context['offset'], length=context['num_bits_offset'], label='Offset')
|
|
827
|
+
pte_answer = ca.AnswerTypes.Binary(context['pte'], length=(context['num_bits_pfn'] + 1), label='PTE')
|
|
828
|
+
if context['is_valid']:
|
|
829
|
+
is_valid_answer = ca.AnswerTypes.String('VALID', label='VALID or INVALID?')
|
|
830
|
+
pfn_answer = ca.AnswerTypes.Binary(context['pfn'], length=context['num_bits_pfn'], label='PFN')
|
|
831
|
+
physical_answer = ca.AnswerTypes.Binary(
|
|
832
|
+
context['physical_address'],
|
|
833
|
+
length=(context['num_bits_pfn'] + context['num_bits_offset']),
|
|
834
|
+
label='Physical Address'
|
|
829
835
|
)
|
|
830
836
|
else:
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
"answer__physical_address": ca.AnswerTypes.String("INVALID", label="Physical Address"),
|
|
836
|
-
}
|
|
837
|
-
)
|
|
838
|
-
|
|
839
|
-
def _get_body(self, *args, **kwargs):
|
|
840
|
-
"""Build question body and collect answers."""
|
|
837
|
+
is_valid_answer = ca.AnswerTypes.String('INVALID', label='VALID or INVALID?')
|
|
838
|
+
pfn_answer = ca.AnswerTypes.String('INVALID', label='PFN')
|
|
839
|
+
physical_answer = ca.AnswerTypes.String('INVALID', label='Physical Address')
|
|
840
|
+
|
|
841
841
|
answers = [
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
842
|
+
vpn_answer,
|
|
843
|
+
offset_answer,
|
|
844
|
+
pte_answer,
|
|
845
|
+
is_valid_answer,
|
|
846
|
+
pfn_answer,
|
|
847
|
+
physical_answer,
|
|
848
848
|
]
|
|
849
849
|
|
|
850
850
|
body = ca.Section()
|
|
@@ -852,73 +852,69 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
852
852
|
body.add_element(
|
|
853
853
|
ca.Paragraph(
|
|
854
854
|
[
|
|
855
|
-
|
|
856
|
-
|
|
855
|
+
'Given the below information please calculate the equivalent physical address of the given virtual address, filling out all steps along the way.',
|
|
856
|
+
'Remember, we typically have the MSB representing valid or invalid.'
|
|
857
857
|
]
|
|
858
858
|
)
|
|
859
859
|
)
|
|
860
860
|
|
|
861
861
|
# Create parameter info table using mixin
|
|
862
862
|
parameter_info = {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
863
|
+
'Virtual Address': f"0b{context['virtual_address']:0{context['num_bits_vpn'] + context['num_bits_offset']}b}",
|
|
864
|
+
'# VPN bits': f"{context['num_bits_vpn']}",
|
|
865
|
+
'# PFN bits': f"{context['num_bits_pfn']}"
|
|
866
866
|
}
|
|
867
867
|
|
|
868
|
-
body.add_element(
|
|
868
|
+
body.add_element(cls.create_info_table(parameter_info))
|
|
869
869
|
|
|
870
|
-
# Use the page table generated in
|
|
870
|
+
# Use the page table generated in _build_context for deterministic output
|
|
871
871
|
# Add in ellipses before and after page table entries, if appropriate
|
|
872
872
|
value_matrix = []
|
|
873
873
|
|
|
874
|
-
if min(
|
|
875
|
-
value_matrix.append([
|
|
874
|
+
if min(context['page_table'].keys()) != 0:
|
|
875
|
+
value_matrix.append(['...', '...'])
|
|
876
876
|
|
|
877
877
|
value_matrix.extend(
|
|
878
878
|
[
|
|
879
|
-
[f"0b{vpn:0{
|
|
880
|
-
for vpn, pte in sorted(
|
|
879
|
+
[f"0b{vpn:0{context['num_bits_vpn']}b}", f"0b{pte:0{(context['num_bits_pfn'] + 1)}b}"]
|
|
880
|
+
for vpn, pte in sorted(context['page_table'].items())
|
|
881
881
|
]
|
|
882
882
|
)
|
|
883
883
|
|
|
884
|
-
if (max(
|
|
885
|
-
value_matrix.append([
|
|
884
|
+
if (max(context['page_table'].keys()) + 1) != 2 ** context['num_bits_vpn']:
|
|
885
|
+
value_matrix.append(['...', '...'])
|
|
886
886
|
|
|
887
887
|
body.add_element(
|
|
888
888
|
ca.Table(
|
|
889
|
-
headers=[
|
|
889
|
+
headers=['VPN', 'PTE'],
|
|
890
890
|
data=value_matrix
|
|
891
891
|
)
|
|
892
892
|
)
|
|
893
893
|
|
|
894
894
|
body.add_element(
|
|
895
895
|
ca.AnswerBlock([
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
896
|
+
vpn_answer,
|
|
897
|
+
offset_answer,
|
|
898
|
+
pte_answer,
|
|
899
|
+
is_valid_answer,
|
|
900
|
+
pfn_answer,
|
|
901
|
+
physical_answer,
|
|
902
902
|
])
|
|
903
903
|
)
|
|
904
904
|
|
|
905
905
|
return body, answers
|
|
906
|
-
|
|
907
|
-
def get_body(self, *args, **kwargs) -> ca.Section:
|
|
908
|
-
"""Build question body (backward compatible interface)."""
|
|
909
|
-
body, _ = self._get_body(*args, **kwargs)
|
|
910
|
-
return body
|
|
911
906
|
|
|
912
|
-
|
|
907
|
+
@classmethod
|
|
908
|
+
def _build_explanation(cls, context):
|
|
913
909
|
"""Build question explanation."""
|
|
914
910
|
explanation = ca.Section()
|
|
915
911
|
|
|
916
912
|
explanation.add_element(
|
|
917
913
|
ca.Paragraph(
|
|
918
914
|
[
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
915
|
+
'The core idea of Paging is we want to break the virtual address into the VPN and the offset. '
|
|
916
|
+
'From here, we get the Page Table Entry corresponding to the VPN, and check the validity of the entry. '
|
|
917
|
+
'If it is valid, we clear the metadata and attach the PFN to the offset and have our physical address.',
|
|
922
918
|
]
|
|
923
919
|
)
|
|
924
920
|
)
|
|
@@ -934,9 +930,9 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
934
930
|
explanation.add_element(
|
|
935
931
|
ca.Paragraph(
|
|
936
932
|
[
|
|
937
|
-
|
|
938
|
-
f"<tt>0b{
|
|
939
|
-
f"= <tt>0b{
|
|
933
|
+
'Virtual Address = VPN | offset',
|
|
934
|
+
f"<tt>0b{context['virtual_address']:0{context['num_bits_vpn'] + context['num_bits_offset']}b}</tt> "
|
|
935
|
+
f"= <tt>0b{context['vpn']:0{context['num_bits_vpn']}b}</tt> | <tt>0b{context['offset']:0{context['num_bits_offset']}b}</tt>",
|
|
940
936
|
]
|
|
941
937
|
)
|
|
942
938
|
)
|
|
@@ -944,19 +940,19 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
944
940
|
explanation.add_element(
|
|
945
941
|
ca.Paragraph(
|
|
946
942
|
[
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
f"<tt>0b{
|
|
950
|
-
|
|
943
|
+
'We next use our VPN to index into our page table and find the corresponding entry.'
|
|
944
|
+
'Our Page Table Entry is ',
|
|
945
|
+
f"<tt>0b{context['pte']:0{(context['num_bits_pfn'] + 1)}b}</tt>"
|
|
946
|
+
'which we found by looking for our VPN in the page table.',
|
|
951
947
|
]
|
|
952
948
|
)
|
|
953
949
|
)
|
|
954
950
|
|
|
955
|
-
if
|
|
951
|
+
if context['is_valid']:
|
|
956
952
|
explanation.add_element(
|
|
957
953
|
ca.Paragraph(
|
|
958
954
|
[
|
|
959
|
-
f"In our PTE we see that the first bit is **{
|
|
955
|
+
f"In our PTE we see that the first bit is **{context['pte'] // (2 ** context['num_bits_pfn'])}** meaning that the translation is **VALID**"
|
|
960
956
|
]
|
|
961
957
|
)
|
|
962
958
|
)
|
|
@@ -964,10 +960,10 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
964
960
|
explanation.add_element(
|
|
965
961
|
ca.Paragraph(
|
|
966
962
|
[
|
|
967
|
-
f"In our PTE we see that the first bit is **{
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
963
|
+
f"In our PTE we see that the first bit is **{context['pte'] // (2 ** context['num_bits_pfn'])}** meaning that the translation is **INVALID**.",
|
|
964
|
+
'Therefore, we just write "INVALID" as our answer.',
|
|
965
|
+
'If it were valid we would complete the below steps.',
|
|
966
|
+
'<hr>'
|
|
971
967
|
]
|
|
972
968
|
)
|
|
973
969
|
)
|
|
@@ -975,18 +971,18 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
975
971
|
explanation.add_element(
|
|
976
972
|
ca.Paragraph(
|
|
977
973
|
[
|
|
978
|
-
|
|
979
|
-
"In this case we
|
|
980
|
-
|
|
981
|
-
|
|
974
|
+
'Next, we convert our PTE to our PFN by removing our metadata. '
|
|
975
|
+
"In this case we are just removing the leading bit. We can do this by applying a binary mask.",
|
|
976
|
+
'PFN = PTE & mask',
|
|
977
|
+
'which is,',
|
|
982
978
|
]
|
|
983
979
|
)
|
|
984
980
|
)
|
|
985
981
|
explanation.add_element(
|
|
986
982
|
ca.Equation(
|
|
987
|
-
f"\\texttt{{{
|
|
988
|
-
f"= \\texttt{{0b{
|
|
989
|
-
f"\\& \\texttt{{0b{(2 **
|
|
983
|
+
f"\\texttt{{{context['pfn']:0{context['num_bits_pfn']}b}}} "
|
|
984
|
+
f"= \\texttt{{0b{context['pte']:0{context['num_bits_pfn'] + 1}b}}} "
|
|
985
|
+
f"\\& \\texttt{{0b{(2 ** context['num_bits_pfn']) - 1:0{context['num_bits_pfn'] + 1}b}}}"
|
|
990
986
|
)
|
|
991
987
|
)
|
|
992
988
|
|
|
@@ -994,21 +990,21 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
994
990
|
[
|
|
995
991
|
ca.Paragraph(
|
|
996
992
|
[
|
|
997
|
-
|
|
998
|
-
|
|
993
|
+
'We then add combine our PFN and offset, '
|
|
994
|
+
'Physical Address = PFN | offset',
|
|
999
995
|
]
|
|
1000
996
|
),
|
|
1001
997
|
ca.Equation(
|
|
1002
|
-
fr"{r'\mathbf{' if
|
|
998
|
+
fr"{r'\mathbf{' if context['is_valid'] else ''}\mathtt{{0b{context['physical_address']:0{context['num_bits_pfn'] + context['num_bits_offset']}b}}}{r'}' if context['is_valid'] else ''} = \mathtt{{0b{context['pfn']:0{context['num_bits_pfn']}b}}} \mid \mathtt{{0b{context['offset']:0{context['num_bits_offset']}b}}}"
|
|
1003
999
|
)
|
|
1004
1000
|
]
|
|
1005
1001
|
)
|
|
1006
1002
|
|
|
1007
1003
|
explanation.add_elements(
|
|
1008
1004
|
[
|
|
1009
|
-
ca.Paragraph([
|
|
1005
|
+
ca.Paragraph(['Note: Strictly speaking, this calculation is:', ]),
|
|
1010
1006
|
ca.Equation(
|
|
1011
|
-
fr"{r'\mathbf{' if
|
|
1007
|
+
fr"{r'\mathbf{' if context['is_valid'] else ''}\mathtt{{0b{context['physical_address']:0{context['num_bits_pfn'] + context['num_bits_offset']}b}}}{r'}' if context['is_valid'] else ''} = \mathtt{{0b{context['pfn']:0{context['num_bits_pfn']}b}{0:0{context['num_bits_offset']}}}} + \mathtt{{0b{context['offset']:0{context['num_bits_offset']}b}}}"
|
|
1012
1008
|
),
|
|
1013
1009
|
ca.Paragraph(["But that's a lot of extra 0s, so I'm splitting them up for succinctness"])
|
|
1014
1010
|
]
|
|
@@ -1016,11 +1012,6 @@ class Paging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
|
1016
1012
|
|
|
1017
1013
|
return explanation, []
|
|
1018
1014
|
|
|
1019
|
-
def get_explanation(self, *args, **kwargs) -> ca.Section:
|
|
1020
|
-
"""Build question explanation (backward compatible interface)."""
|
|
1021
|
-
explanation, _ = self._get_explanation(*args, **kwargs)
|
|
1022
|
-
return explanation
|
|
1023
|
-
|
|
1024
1015
|
|
|
1025
1016
|
@QuestionRegistry.register()
|
|
1026
1017
|
class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
@@ -1034,246 +1025,270 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
|
|
|
1034
1025
|
MAX_PTI_BITS = 3
|
|
1035
1026
|
MAX_PFN_BITS = 6
|
|
1036
1027
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1028
|
+
@classmethod
|
|
1029
|
+
def _build_context(cls, *, rng_seed=None, **kwargs):
|
|
1030
|
+
rng = random.Random(rng_seed)
|
|
1039
1031
|
|
|
1040
1032
|
# Set up bit counts
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1033
|
+
num_bits_offset = rng.randint(cls.MIN_OFFSET_BITS, cls.MAX_OFFSET_BITS)
|
|
1034
|
+
num_bits_pdi = rng.randint(cls.MIN_PDI_BITS, cls.MAX_PDI_BITS)
|
|
1035
|
+
num_bits_pti = rng.randint(cls.MIN_PTI_BITS, cls.MAX_PTI_BITS)
|
|
1036
|
+
num_bits_pfn = rng.randint(cls.MIN_PFN_BITS, cls.MAX_PFN_BITS)
|
|
1045
1037
|
|
|
1046
1038
|
# Total VPN bits = PDI + PTI
|
|
1047
|
-
|
|
1048
|
-
|
|
1039
|
+
num_bits_vpn = num_bits_pdi + num_bits_pti
|
|
1040
|
+
|
|
1049
1041
|
# Generate a random virtual address
|
|
1050
|
-
|
|
1042
|
+
virtual_address = rng.randint(1, 2 ** (num_bits_vpn + num_bits_offset))
|
|
1051
1043
|
|
|
1052
1044
|
# Extract components from virtual address
|
|
1053
|
-
|
|
1054
|
-
vpn =
|
|
1045
|
+
offset = virtual_address % (2 ** num_bits_offset)
|
|
1046
|
+
vpn = virtual_address // (2 ** num_bits_offset)
|
|
1055
1047
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1048
|
+
pti = vpn % (2 ** num_bits_pti)
|
|
1049
|
+
pdi = vpn // (2 ** num_bits_pti)
|
|
1058
1050
|
|
|
1059
1051
|
# Generate PFN randomly
|
|
1060
|
-
|
|
1052
|
+
pfn = rng.randint(0, 2 ** num_bits_pfn - 1)
|
|
1061
1053
|
|
|
1062
1054
|
# Calculate physical address
|
|
1063
|
-
|
|
1055
|
+
physical_address = pfn * (2 ** num_bits_offset) + offset
|
|
1064
1056
|
|
|
1065
1057
|
# Determine validity at both levels
|
|
1066
1058
|
# PD entry can be valid or invalid
|
|
1067
|
-
|
|
1059
|
+
pd_valid = rng.choices([True, False], weights=[cls.PROBABILITY_OF_VALID, 1 - cls.PROBABILITY_OF_VALID], k=1)[0]
|
|
1068
1060
|
|
|
1069
1061
|
# PT entry only matters if PD is valid
|
|
1070
|
-
if
|
|
1071
|
-
|
|
1062
|
+
if pd_valid:
|
|
1063
|
+
pt_valid = rng.choices([True, False], weights=[cls.PROBABILITY_OF_VALID, 1 - cls.PROBABILITY_OF_VALID], k=1)[0]
|
|
1072
1064
|
else:
|
|
1073
|
-
|
|
1065
|
+
pt_valid = False # Doesn't matter, won't be checked
|
|
1074
1066
|
|
|
1075
1067
|
# Generate a page table number (PTBR - Page Table Base Register value in the PD entry)
|
|
1076
1068
|
# This represents which page table to use
|
|
1077
|
-
|
|
1069
|
+
page_table_number = rng.randint(0, 2 ** num_bits_pfn - 1)
|
|
1078
1070
|
|
|
1079
1071
|
# Create PD entry: valid bit + page table number
|
|
1080
|
-
if
|
|
1081
|
-
|
|
1072
|
+
if pd_valid:
|
|
1073
|
+
pd_entry = (2 ** num_bits_pfn) + page_table_number
|
|
1082
1074
|
else:
|
|
1083
|
-
|
|
1075
|
+
pd_entry = page_table_number # Invalid, no valid bit set
|
|
1084
1076
|
|
|
1085
1077
|
# Create PT entry: valid bit + PFN
|
|
1086
|
-
if
|
|
1087
|
-
|
|
1078
|
+
if pt_valid:
|
|
1079
|
+
pte = (2 ** num_bits_pfn) + pfn
|
|
1088
1080
|
else:
|
|
1089
|
-
|
|
1081
|
+
pte = pfn # Invalid, no valid bit set
|
|
1090
1082
|
|
|
1091
1083
|
# Overall validity requires both levels to be valid
|
|
1092
|
-
|
|
1084
|
+
is_valid = pd_valid and pt_valid
|
|
1093
1085
|
|
|
1094
1086
|
# Build page directory - show 3-4 entries
|
|
1095
|
-
pd_size =
|
|
1096
|
-
lowest_pd_bottom = max([0,
|
|
1097
|
-
highest_pd_bottom = min([2 **
|
|
1098
|
-
pd_bottom =
|
|
1087
|
+
pd_size = rng.randint(3, 4)
|
|
1088
|
+
lowest_pd_bottom = max([0, pdi - pd_size])
|
|
1089
|
+
highest_pd_bottom = min([2 ** num_bits_pdi - pd_size, pdi])
|
|
1090
|
+
pd_bottom = rng.randint(lowest_pd_bottom, highest_pd_bottom)
|
|
1099
1091
|
pd_top = pd_bottom + pd_size
|
|
1100
1092
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1093
|
+
page_directory = {}
|
|
1094
|
+
page_directory[pdi] = pd_entry
|
|
1103
1095
|
|
|
1104
1096
|
# Fill in other PD entries
|
|
1105
|
-
for
|
|
1106
|
-
if
|
|
1097
|
+
for pdi_idx in range(pd_bottom, pd_top):
|
|
1098
|
+
if pdi_idx == pdi:
|
|
1107
1099
|
continue
|
|
1108
1100
|
# Generate random PD entry
|
|
1109
|
-
pt_num =
|
|
1110
|
-
while pt_num ==
|
|
1111
|
-
pt_num =
|
|
1101
|
+
pt_num = rng.randint(0, 2 ** num_bits_pfn - 1)
|
|
1102
|
+
while pt_num == page_table_number: # Make sure it's different
|
|
1103
|
+
pt_num = rng.randint(0, 2 ** num_bits_pfn - 1)
|
|
1112
1104
|
|
|
1113
1105
|
# Randomly valid or invalid
|
|
1114
|
-
if
|
|
1115
|
-
pd_val = (2 **
|
|
1106
|
+
if rng.choices([True, False], weights=[cls.PROBABILITY_OF_VALID, 1 - cls.PROBABILITY_OF_VALID], k=1)[0]:
|
|
1107
|
+
pd_val = (2 ** num_bits_pfn) + pt_num
|
|
1116
1108
|
else:
|
|
1117
1109
|
pd_val = pt_num
|
|
1118
1110
|
|
|
1119
|
-
|
|
1111
|
+
page_directory[pdi_idx] = pd_val
|
|
1120
1112
|
|
|
1121
1113
|
# Build 2-3 page tables to show
|
|
1122
1114
|
# Always include the one we need, plus 1-2 others
|
|
1123
|
-
num_page_tables_to_show =
|
|
1115
|
+
num_page_tables_to_show = rng.randint(2, 3)
|
|
1124
1116
|
|
|
1125
1117
|
# Get unique page table numbers from the PD entries (extract PT numbers from valid entries)
|
|
1126
1118
|
shown_pt_numbers = set()
|
|
1127
|
-
for
|
|
1128
|
-
pt_num = pd_val % (2 **
|
|
1119
|
+
for pd_val in page_directory.values():
|
|
1120
|
+
pt_num = pd_val % (2 ** num_bits_pfn) # Extract PT number (remove valid bit)
|
|
1129
1121
|
shown_pt_numbers.add(pt_num)
|
|
1130
1122
|
|
|
1131
1123
|
# Ensure our required page table is included
|
|
1132
|
-
shown_pt_numbers.add(
|
|
1124
|
+
shown_pt_numbers.add(page_table_number)
|
|
1133
1125
|
|
|
1134
1126
|
# Limit to requested number, but ALWAYS keep the required page table
|
|
1135
1127
|
shown_pt_numbers_list = list(shown_pt_numbers)
|
|
1136
|
-
if
|
|
1128
|
+
if page_table_number in shown_pt_numbers_list:
|
|
1137
1129
|
# Remove it temporarily so we can add it back first
|
|
1138
|
-
shown_pt_numbers_list.remove(
|
|
1130
|
+
shown_pt_numbers_list.remove(page_table_number)
|
|
1139
1131
|
# Start with required page table, then add others up to the limit
|
|
1140
|
-
shown_pt_numbers = [
|
|
1132
|
+
shown_pt_numbers = [page_table_number] + shown_pt_numbers_list[:num_page_tables_to_show - 1]
|
|
1141
1133
|
|
|
1142
1134
|
# Build each page table
|
|
1143
|
-
|
|
1135
|
+
page_tables = {} # Dict mapping PT number -> dict of PTI -> PTE
|
|
1144
1136
|
|
|
1145
1137
|
# Use consistent size for all page tables for cleaner presentation
|
|
1146
|
-
pt_size =
|
|
1138
|
+
pt_size = rng.randint(2, 4)
|
|
1147
1139
|
|
|
1148
1140
|
# Determine the PTI range that all tables will use (based on target PTI)
|
|
1149
1141
|
# This ensures all tables show the same PTI values for consistency
|
|
1150
|
-
lowest_pt_bottom = max([0,
|
|
1151
|
-
highest_pt_bottom = min([2 **
|
|
1152
|
-
pt_bottom =
|
|
1142
|
+
lowest_pt_bottom = max([0, pti - pt_size + 1])
|
|
1143
|
+
highest_pt_bottom = min([2 ** num_bits_pti - pt_size, pti])
|
|
1144
|
+
pt_bottom = rng.randint(lowest_pt_bottom, highest_pt_bottom)
|
|
1153
1145
|
pt_top = pt_bottom + pt_size
|
|
1154
1146
|
|
|
1155
1147
|
# Generate all page tables using the SAME PTI range
|
|
1156
1148
|
for pt_num in shown_pt_numbers:
|
|
1157
|
-
|
|
1149
|
+
page_tables[pt_num] = {}
|
|
1158
1150
|
|
|
1159
|
-
for
|
|
1160
|
-
if pt_num ==
|
|
1151
|
+
for pti_idx in range(pt_bottom, pt_top):
|
|
1152
|
+
if pt_num == page_table_number and pti_idx == pti:
|
|
1161
1153
|
# Use the actual answer for the target page table entry
|
|
1162
|
-
|
|
1154
|
+
page_tables[pt_num][pti_idx] = pte
|
|
1163
1155
|
else:
|
|
1164
1156
|
# Generate random PTE for all other entries
|
|
1165
|
-
|
|
1166
|
-
if
|
|
1167
|
-
pte_val = (2 **
|
|
1157
|
+
pfn_rand = rng.randint(0, 2 ** num_bits_pfn - 1)
|
|
1158
|
+
if rng.choices([True, False], weights=[cls.PROBABILITY_OF_VALID, 1 - cls.PROBABILITY_OF_VALID], k=1)[0]:
|
|
1159
|
+
pte_val = (2 ** num_bits_pfn) + pfn_rand
|
|
1168
1160
|
else:
|
|
1169
|
-
pte_val =
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
)
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1161
|
+
pte_val = pfn_rand
|
|
1162
|
+
|
|
1163
|
+
page_tables[pt_num][pti_idx] = pte_val
|
|
1164
|
+
|
|
1165
|
+
def random_pte_value():
|
|
1166
|
+
pfn_rand = rng.randint(0, 2 ** num_bits_pfn - 1)
|
|
1167
|
+
if rng.choices([True, False], weights=[cls.PROBABILITY_OF_VALID, 1 - cls.PROBABILITY_OF_VALID], k=1)[0]:
|
|
1168
|
+
return (2 ** num_bits_pfn) + pfn_rand
|
|
1169
|
+
return pfn_rand
|
|
1170
|
+
|
|
1171
|
+
pt_display_extras = {}
|
|
1172
|
+
for pt_num, pt_entries in page_tables.items():
|
|
1173
|
+
min_pti = min(pt_entries.keys())
|
|
1174
|
+
max_pti = max(pt_entries.keys())
|
|
1175
|
+
max_possible_pti = 2 ** num_bits_pti - 1
|
|
1176
|
+
leading = None
|
|
1177
|
+
trailing = None
|
|
1178
|
+
if min_pti == 1:
|
|
1179
|
+
leading = (0, random_pte_value())
|
|
1180
|
+
if (max_possible_pti - max_pti) == 1:
|
|
1181
|
+
trailing = (max_possible_pti, random_pte_value())
|
|
1182
|
+
pt_display_extras[pt_num] = {
|
|
1183
|
+
'leading': leading,
|
|
1184
|
+
'trailing': trailing,
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
return {
|
|
1188
|
+
'num_bits_offset': num_bits_offset,
|
|
1189
|
+
'num_bits_pdi': num_bits_pdi,
|
|
1190
|
+
'num_bits_pti': num_bits_pti,
|
|
1191
|
+
'num_bits_pfn': num_bits_pfn,
|
|
1192
|
+
'num_bits_vpn': num_bits_vpn,
|
|
1193
|
+
'virtual_address': virtual_address,
|
|
1194
|
+
'offset': offset,
|
|
1195
|
+
'pdi': pdi,
|
|
1196
|
+
'pti': pti,
|
|
1197
|
+
'pfn': pfn,
|
|
1198
|
+
'physical_address': physical_address,
|
|
1199
|
+
'pd_valid': pd_valid,
|
|
1200
|
+
'pt_valid': pt_valid,
|
|
1201
|
+
'page_table_number': page_table_number,
|
|
1202
|
+
'pd_entry': pd_entry,
|
|
1203
|
+
'pte': pte,
|
|
1204
|
+
'is_valid': is_valid,
|
|
1205
|
+
'page_directory': page_directory,
|
|
1206
|
+
'page_tables': page_tables,
|
|
1207
|
+
'pt_display_extras': pt_display_extras,
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
@classmethod
|
|
1211
|
+
def _build_body(cls, context):
|
|
1212
|
+
"""Build question body and collect answers."""
|
|
1213
|
+
pdi_answer = ca.AnswerTypes.Binary(context['pdi'], length=context['num_bits_pdi'], label='PDI (Page Directory Index)')
|
|
1214
|
+
pti_answer = ca.AnswerTypes.Binary(context['pti'], length=context['num_bits_pti'], label='PTI (Page Table Index)')
|
|
1215
|
+
offset_answer = ca.AnswerTypes.Binary(context['offset'], length=context['num_bits_offset'], label='Offset')
|
|
1216
|
+
pd_entry_answer = ca.AnswerTypes.Binary(context['pd_entry'], length=(context['num_bits_pfn'] + 1), label='PD Entry (from Page Directory)')
|
|
1217
|
+
if context['pd_valid']:
|
|
1218
|
+
pt_number_answer = ca.AnswerTypes.Binary(context['page_table_number'], length=context['num_bits_pfn'], label='Page Table Number')
|
|
1219
|
+
pte_answer = ca.AnswerTypes.Binary(context['pte'], length=(context['num_bits_pfn'] + 1), label='PTE (from Page Table)')
|
|
1198
1220
|
else:
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
"answer__physical_address": ca.AnswerTypes.Binary(self.physical_address, length=(self.num_bits_pfn + self.num_bits_offset), label="Physical Address"
|
|
1211
|
-
),
|
|
1212
|
-
})
|
|
1221
|
+
pt_number_answer = ca.AnswerTypes.String('INVALID', label='Page Table Number')
|
|
1222
|
+
pte_answer = ca.AnswerTypes.String(['INVALID', 'N/A'], label='PTE (from Page Table)')
|
|
1223
|
+
|
|
1224
|
+
if context['pd_valid'] and context['pt_valid']:
|
|
1225
|
+
is_valid_answer = ca.AnswerTypes.String('VALID', label='VALID or INVALID?')
|
|
1226
|
+
pfn_answer = ca.AnswerTypes.Binary(context['pfn'], length=context['num_bits_pfn'], label='PFN')
|
|
1227
|
+
physical_answer = ca.AnswerTypes.Binary(
|
|
1228
|
+
context['physical_address'],
|
|
1229
|
+
length=(context['num_bits_pfn'] + context['num_bits_offset']),
|
|
1230
|
+
label='Physical Address'
|
|
1231
|
+
)
|
|
1213
1232
|
else:
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
"answer__physical_address": ca.AnswerTypes.String("INVALID", label="Physical Address"),
|
|
1218
|
-
})
|
|
1233
|
+
is_valid_answer = ca.AnswerTypes.String('INVALID', label='VALID or INVALID?')
|
|
1234
|
+
pfn_answer = ca.AnswerTypes.String('INVALID', label='PFN')
|
|
1235
|
+
physical_answer = ca.AnswerTypes.String('INVALID', label='Physical Address')
|
|
1219
1236
|
|
|
1220
|
-
def _get_body(self, *args, **kwargs):
|
|
1221
|
-
"""Build question body and collect answers."""
|
|
1222
1237
|
answers = [
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1238
|
+
pdi_answer,
|
|
1239
|
+
pti_answer,
|
|
1240
|
+
offset_answer,
|
|
1241
|
+
pd_entry_answer,
|
|
1242
|
+
pt_number_answer,
|
|
1243
|
+
pte_answer,
|
|
1244
|
+
is_valid_answer,
|
|
1245
|
+
pfn_answer,
|
|
1246
|
+
physical_answer,
|
|
1232
1247
|
]
|
|
1233
1248
|
|
|
1234
1249
|
body = ca.Section()
|
|
1235
1250
|
|
|
1236
1251
|
body.add_element(
|
|
1237
1252
|
ca.Paragraph([
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1253
|
+
'Given the below information please calculate the equivalent physical address of the given virtual address, filling out all steps along the way.',
|
|
1254
|
+
'This problem uses **two-level (hierarchical) paging**.',
|
|
1255
|
+
'Remember, we typically have the MSB representing valid or invalid.'
|
|
1241
1256
|
])
|
|
1242
1257
|
)
|
|
1243
1258
|
|
|
1244
1259
|
# Create parameter info table using mixin (same format as Paging question)
|
|
1245
1260
|
parameter_info = {
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1261
|
+
'Virtual Address': f"0b{context['virtual_address']:0{context['num_bits_vpn'] + context['num_bits_offset']}b}",
|
|
1262
|
+
'# PDI bits': f"{context['num_bits_pdi']}",
|
|
1263
|
+
'# PTI bits': f"{context['num_bits_pti']}",
|
|
1264
|
+
'# Offset bits': f"{context['num_bits_offset']}",
|
|
1265
|
+
'# PFN bits': f"{context['num_bits_pfn']}"
|
|
1251
1266
|
}
|
|
1252
1267
|
|
|
1253
|
-
body.add_element(
|
|
1268
|
+
body.add_element(cls.create_info_table(parameter_info))
|
|
1254
1269
|
|
|
1255
1270
|
# Page Directory table
|
|
1256
1271
|
pd_matrix = []
|
|
1257
|
-
if min(
|
|
1258
|
-
pd_matrix.append([
|
|
1272
|
+
if min(context['page_directory'].keys()) != 0:
|
|
1273
|
+
pd_matrix.append(['...', '...'])
|
|
1259
1274
|
|
|
1260
1275
|
pd_matrix.extend([
|
|
1261
|
-
[f"0b{pdi:0{
|
|
1262
|
-
for pdi, pd_val in sorted(
|
|
1276
|
+
[f"0b{pdi:0{context['num_bits_pdi']}b}", f"0b{pd_val:0{context['num_bits_pfn'] + 1}b}"]
|
|
1277
|
+
for pdi, pd_val in sorted(context['page_directory'].items())
|
|
1263
1278
|
])
|
|
1264
1279
|
|
|
1265
|
-
if (max(
|
|
1266
|
-
pd_matrix.append([
|
|
1280
|
+
if (max(context['page_directory'].keys()) + 1) != 2 ** context['num_bits_pdi']:
|
|
1281
|
+
pd_matrix.append(['...', '...'])
|
|
1267
1282
|
|
|
1268
1283
|
# Use a simple text paragraph - the bold will come from markdown conversion
|
|
1269
1284
|
body.add_element(
|
|
1270
1285
|
ca.Paragraph([
|
|
1271
|
-
|
|
1286
|
+
'**Page Directory:**'
|
|
1272
1287
|
])
|
|
1273
1288
|
)
|
|
1274
1289
|
body.add_element(
|
|
1275
1290
|
ca.Table(
|
|
1276
|
-
headers=[
|
|
1291
|
+
headers=['PDI', 'PD Entry'],
|
|
1277
1292
|
data=pd_matrix
|
|
1278
1293
|
)
|
|
1279
1294
|
)
|
|
@@ -1281,49 +1296,43 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
|
|
|
1281
1296
|
# Page Tables - use TableGroup for side-by-side display
|
|
1282
1297
|
table_group = ca.TableGroup()
|
|
1283
1298
|
|
|
1284
|
-
for pt_num in sorted(
|
|
1299
|
+
for pt_num in sorted(context['page_tables'].keys()):
|
|
1285
1300
|
pt_matrix = []
|
|
1286
|
-
pt_entries =
|
|
1301
|
+
pt_entries = context['page_tables'][pt_num]
|
|
1287
1302
|
|
|
1288
1303
|
min_pti = min(pt_entries.keys())
|
|
1289
1304
|
max_pti = max(pt_entries.keys())
|
|
1290
|
-
max_possible_pti = 2 **
|
|
1305
|
+
max_possible_pti = 2 ** context['num_bits_pti'] - 1
|
|
1291
1306
|
|
|
1292
1307
|
# Smart leading ellipsis: only if there are 2+ hidden entries before
|
|
1293
1308
|
# (if only 1 hidden, we should just show it)
|
|
1294
1309
|
if min_pti > 1:
|
|
1295
|
-
pt_matrix.append([
|
|
1310
|
+
pt_matrix.append(['...', '...'])
|
|
1296
1311
|
elif min_pti == 1:
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
else:
|
|
1302
|
-
pte_val = pfn
|
|
1303
|
-
pt_matrix.append([f"0b{0:0{self.num_bits_pti}b}", f"0b{pte_val:0{self.num_bits_pfn + 1}b}"])
|
|
1312
|
+
leading = context['pt_display_extras'][pt_num]['leading']
|
|
1313
|
+
if leading is not None:
|
|
1314
|
+
leading_pti, leading_pte = leading
|
|
1315
|
+
pt_matrix.append([f"0b{leading_pti:0{context['num_bits_pti']}b}", f"0b{leading_pte:0{context['num_bits_pfn'] + 1}b}"])
|
|
1304
1316
|
|
|
1305
1317
|
# Add actual entries
|
|
1306
1318
|
pt_matrix.extend([
|
|
1307
|
-
[f"0b{pti:0{
|
|
1319
|
+
[f"0b{pti:0{context['num_bits_pti']}b}", f"0b{pte:0{context['num_bits_pfn'] + 1}b}"]
|
|
1308
1320
|
for pti, pte in sorted(pt_entries.items())
|
|
1309
1321
|
])
|
|
1310
1322
|
|
|
1311
1323
|
# Smart trailing ellipsis: only if there are 2+ hidden entries after
|
|
1312
1324
|
hidden_after = max_possible_pti - max_pti
|
|
1313
1325
|
if hidden_after > 1:
|
|
1314
|
-
pt_matrix.append([
|
|
1326
|
+
pt_matrix.append(['...', '...'])
|
|
1315
1327
|
elif hidden_after == 1:
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
else:
|
|
1321
|
-
pte_val = pfn
|
|
1322
|
-
pt_matrix.append([f"0b{max_possible_pti:0{self.num_bits_pti}b}", f"0b{pte_val:0{self.num_bits_pfn + 1}b}"])
|
|
1328
|
+
trailing = context['pt_display_extras'][pt_num]['trailing']
|
|
1329
|
+
if trailing is not None:
|
|
1330
|
+
trailing_pti, trailing_pte = trailing
|
|
1331
|
+
pt_matrix.append([f"0b{trailing_pti:0{context['num_bits_pti']}b}", f"0b{trailing_pte:0{context['num_bits_pfn'] + 1}b}"])
|
|
1323
1332
|
|
|
1324
1333
|
table_group.add_table(
|
|
1325
|
-
label=f"PTC 0b{pt_num:0{
|
|
1326
|
-
table=ca.Table(headers=[
|
|
1334
|
+
label=f"PTC 0b{pt_num:0{context['num_bits_pfn']}b}:",
|
|
1335
|
+
table=ca.Table(headers=['PTI', 'PTE'], data=pt_matrix)
|
|
1327
1336
|
)
|
|
1328
1337
|
|
|
1329
1338
|
body.add_element(table_group)
|
|
@@ -1331,33 +1340,29 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
|
|
|
1331
1340
|
# Answer block
|
|
1332
1341
|
body.add_element(
|
|
1333
1342
|
ca.AnswerBlock([
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
+
pdi_answer,
|
|
1344
|
+
pti_answer,
|
|
1345
|
+
offset_answer,
|
|
1346
|
+
pd_entry_answer,
|
|
1347
|
+
pt_number_answer,
|
|
1348
|
+
pte_answer,
|
|
1349
|
+
is_valid_answer,
|
|
1350
|
+
pfn_answer,
|
|
1351
|
+
physical_answer,
|
|
1343
1352
|
])
|
|
1344
1353
|
)
|
|
1345
1354
|
|
|
1346
1355
|
return body, answers
|
|
1347
1356
|
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
body, _ = self._get_body(*args, **kwargs)
|
|
1351
|
-
return body
|
|
1352
|
-
|
|
1353
|
-
def _get_explanation(self, *args, **kwargs):
|
|
1357
|
+
@classmethod
|
|
1358
|
+
def _build_explanation(cls, context):
|
|
1354
1359
|
"""Build question explanation."""
|
|
1355
1360
|
explanation = ca.Section()
|
|
1356
1361
|
|
|
1357
1362
|
explanation.add_element(
|
|
1358
1363
|
ca.Paragraph([
|
|
1359
|
-
|
|
1360
|
-
|
|
1364
|
+
'Two-level paging requires two lookups: first in the Page Directory, then in a Page Table.',
|
|
1365
|
+
'The virtual address is split into three parts: PDI | PTI | Offset.'
|
|
1361
1366
|
])
|
|
1362
1367
|
)
|
|
1363
1368
|
|
|
@@ -1370,128 +1375,123 @@ class HierarchicalPaging(MemoryAccessQuestion, TableQuestionMixin, BodyTemplates
|
|
|
1370
1375
|
# Step 1: Extract PDI, PTI, Offset
|
|
1371
1376
|
explanation.add_element(
|
|
1372
1377
|
ca.Paragraph([
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
f"<tt>0b{
|
|
1376
|
-
f"<tt>0b{
|
|
1377
|
-
f"<tt>0b{
|
|
1378
|
-
f"<tt>0b{
|
|
1378
|
+
'**Step 1: Extract components from Virtual Address**',
|
|
1379
|
+
'Virtual Address = PDI | PTI | Offset',
|
|
1380
|
+
f"<tt>0b{context['virtual_address']:0{context['num_bits_vpn'] + context['num_bits_offset']}b}</tt> = "
|
|
1381
|
+
f"<tt>0b{context['pdi']:0{context['num_bits_pdi']}b}</tt> | "
|
|
1382
|
+
f"<tt>0b{context['pti']:0{context['num_bits_pti']}b}</tt> | "
|
|
1383
|
+
f"<tt>0b{context['offset']:0{context['num_bits_offset']}b}</tt>"
|
|
1379
1384
|
])
|
|
1380
1385
|
)
|
|
1381
1386
|
|
|
1382
1387
|
# Step 2: Look up PD Entry
|
|
1383
1388
|
explanation.add_element(
|
|
1384
1389
|
ca.Paragraph([
|
|
1385
|
-
|
|
1386
|
-
f"Using PDI = <tt>0b{
|
|
1390
|
+
'**Step 2: Look up Page Directory Entry**',
|
|
1391
|
+
f"Using PDI = <tt>0b{context['pdi']:0{context['num_bits_pdi']}b}</tt>, we find PD Entry = <tt>0b{context['pd_entry']:0{context['num_bits_pfn'] + 1}b}</tt>"
|
|
1387
1392
|
])
|
|
1388
1393
|
)
|
|
1389
1394
|
|
|
1390
1395
|
# Step 3: Check PD validity
|
|
1391
|
-
pd_valid_bit =
|
|
1396
|
+
pd_valid_bit = context['pd_entry'] // (2 ** context['num_bits_pfn'])
|
|
1392
1397
|
explanation.add_element(
|
|
1393
1398
|
ca.Paragraph([
|
|
1394
|
-
|
|
1395
|
-
f"The MSB (valid bit) is **{pd_valid_bit}**, so this PD Entry is **{'VALID' if
|
|
1399
|
+
'**Step 3: Check Page Directory Entry validity**',
|
|
1400
|
+
f"The MSB (valid bit) is **{pd_valid_bit}**, so this PD Entry is **{'VALID' if context['pd_valid'] else 'INVALID'}**."
|
|
1396
1401
|
])
|
|
1397
1402
|
)
|
|
1398
1403
|
|
|
1399
|
-
if not
|
|
1404
|
+
if not context['pd_valid']:
|
|
1400
1405
|
explanation.add_element(
|
|
1401
1406
|
ca.Paragraph([
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1407
|
+
'Since the Page Directory Entry is invalid, the translation fails here.',
|
|
1408
|
+
'We write **INVALID** for all remaining fields.',
|
|
1409
|
+
'If it were valid, we would continue with the steps below.',
|
|
1410
|
+
'<hr>'
|
|
1406
1411
|
])
|
|
1407
1412
|
)
|
|
1408
1413
|
|
|
1409
1414
|
# Step 4: Extract PT number (if PD valid)
|
|
1410
1415
|
explanation.add_element(
|
|
1411
1416
|
ca.Paragraph([
|
|
1412
|
-
|
|
1413
|
-
|
|
1417
|
+
'**Step 4: Extract Page Table Number**',
|
|
1418
|
+
'We remove the valid bit from the PD Entry to get the Page Table Number:'
|
|
1414
1419
|
])
|
|
1415
1420
|
)
|
|
1416
1421
|
|
|
1417
1422
|
explanation.add_element(
|
|
1418
1423
|
ca.Equation(
|
|
1419
|
-
f"\\texttt{{{
|
|
1420
|
-
f"\\texttt{{0b{
|
|
1421
|
-
f"\\texttt{{0b{(2 **
|
|
1424
|
+
f"\\texttt{{{context['page_table_number']:0{context['num_bits_pfn']}b}}} = "
|
|
1425
|
+
f"\\texttt{{0b{context['pd_entry']:0{context['num_bits_pfn'] + 1}b}}} \\& "
|
|
1426
|
+
f"\\texttt{{0b{(2 ** context['num_bits_pfn']) - 1:0{context['num_bits_pfn'] + 1}b}}}"
|
|
1422
1427
|
)
|
|
1423
1428
|
)
|
|
1424
1429
|
|
|
1425
|
-
if
|
|
1430
|
+
if context['pd_valid']:
|
|
1426
1431
|
explanation.add_element(
|
|
1427
1432
|
ca.Paragraph([
|
|
1428
|
-
f"This tells us to use **Page Table #{
|
|
1433
|
+
f"This tells us to use **Page Table #{context['page_table_number']}**."
|
|
1429
1434
|
])
|
|
1430
1435
|
)
|
|
1431
1436
|
|
|
1432
1437
|
# Step 5: Look up PTE
|
|
1433
1438
|
explanation.add_element(
|
|
1434
1439
|
ca.Paragraph([
|
|
1435
|
-
|
|
1436
|
-
f"Using PTI = <tt>0b{
|
|
1437
|
-
f"we find PTE = <tt>0b{
|
|
1440
|
+
'**Step 5: Look up Page Table Entry**',
|
|
1441
|
+
f"Using PTI = <tt>0b{context['pti']:0{context['num_bits_pti']}b}</tt> in Page Table #{context['page_table_number']}, "
|
|
1442
|
+
f"we find PTE = <tt>0b{context['pte']:0{context['num_bits_pfn'] + 1}b}</tt>"
|
|
1438
1443
|
])
|
|
1439
1444
|
)
|
|
1440
1445
|
|
|
1441
1446
|
# Step 6: Check PT validity
|
|
1442
|
-
pt_valid_bit =
|
|
1447
|
+
pt_valid_bit = context['pte'] // (2 ** context['num_bits_pfn'])
|
|
1443
1448
|
explanation.add_element(
|
|
1444
1449
|
ca.Paragraph([
|
|
1445
|
-
|
|
1446
|
-
f"The MSB (valid bit) is **{pt_valid_bit}**, so this PTE is **{'VALID' if
|
|
1450
|
+
'**Step 6: Check Page Table Entry validity**',
|
|
1451
|
+
f"The MSB (valid bit) is **{pt_valid_bit}**, so this PTE is **{'VALID' if context['pt_valid'] else 'INVALID'}**."
|
|
1447
1452
|
])
|
|
1448
1453
|
)
|
|
1449
1454
|
|
|
1450
|
-
if not
|
|
1455
|
+
if not context['pt_valid']:
|
|
1451
1456
|
explanation.add_element(
|
|
1452
1457
|
ca.Paragraph([
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1458
|
+
'Since the Page Table Entry is invalid, the translation fails.',
|
|
1459
|
+
'We write **INVALID** for PFN and Physical Address.',
|
|
1460
|
+
'If it were valid, we would continue with the steps below.',
|
|
1461
|
+
'<hr>'
|
|
1457
1462
|
])
|
|
1458
1463
|
)
|
|
1459
1464
|
|
|
1460
1465
|
# Step 7: Extract PFN
|
|
1461
1466
|
explanation.add_element(
|
|
1462
1467
|
ca.Paragraph([
|
|
1463
|
-
|
|
1464
|
-
|
|
1468
|
+
'**Step 7: Extract PFN**',
|
|
1469
|
+
'We remove the valid bit from the PTE to get the PFN:'
|
|
1465
1470
|
])
|
|
1466
1471
|
)
|
|
1467
1472
|
|
|
1468
1473
|
explanation.add_element(
|
|
1469
1474
|
ca.Equation(
|
|
1470
|
-
f"\\texttt{{{
|
|
1471
|
-
f"\\texttt{{0b{
|
|
1472
|
-
f"\\texttt{{0b{(2 **
|
|
1475
|
+
f"\\texttt{{{context['pfn']:0{context['num_bits_pfn']}b}}} = "
|
|
1476
|
+
f"\\texttt{{0b{context['pte']:0{context['num_bits_pfn'] + 1}b}}} \\& "
|
|
1477
|
+
f"\\texttt{{0b{(2 ** context['num_bits_pfn']) - 1:0{context['num_bits_pfn'] + 1}b}}}"
|
|
1473
1478
|
)
|
|
1474
1479
|
)
|
|
1475
1480
|
|
|
1476
1481
|
# Step 8: Construct physical address
|
|
1477
1482
|
explanation.add_element(
|
|
1478
1483
|
ca.Paragraph([
|
|
1479
|
-
|
|
1480
|
-
|
|
1484
|
+
'**Step 8: Construct Physical Address**',
|
|
1485
|
+
'Physical Address = PFN | Offset'
|
|
1481
1486
|
])
|
|
1482
1487
|
)
|
|
1483
1488
|
|
|
1484
1489
|
explanation.add_element(
|
|
1485
1490
|
ca.Equation(
|
|
1486
|
-
fr"{r'\mathbf{' if
|
|
1487
|
-
f"
|
|
1488
|
-
f"
|
|
1491
|
+
fr"{r'\mathbf{' if context['is_valid'] else ''}\mathtt{{0b{context['physical_address']:0{context['num_bits_pfn'] + context['num_bits_offset']}b}}}{r'}' if context['is_valid'] else ''} = "
|
|
1492
|
+
f"\mathtt{{0b{context['pfn']:0{context['num_bits_pfn']}b}}} \mid "
|
|
1493
|
+
f"\mathtt{{0b{context['offset']:0{context['num_bits_offset']}b}}}"
|
|
1489
1494
|
)
|
|
1490
1495
|
)
|
|
1491
1496
|
|
|
1492
1497
|
return explanation, []
|
|
1493
|
-
|
|
1494
|
-
def get_explanation(self, *args, **kwargs) -> ca.Section:
|
|
1495
|
-
"""Build question explanation (backward compatible interface)."""
|
|
1496
|
-
explanation, _ = self._get_explanation(*args, **kwargs)
|
|
1497
|
-
return explanation
|