仿真平台内核初版 -tlib库 包含<sparc arm riscv powerPC>
This commit is contained in:
3
tools/PeakRDL-renode/tests/.gitignore
vendored
Normal file
3
tools/PeakRDL-renode/tests/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
bin
|
||||
obj
|
||||
generated
|
||||
608
tools/PeakRDL-renode/tests/SystemRDLGenTest.cs
Normal file
608
tools/PeakRDL-renode/tests/SystemRDLGenTest.cs
Normal file
@@ -0,0 +1,608 @@
|
||||
// Copyright (C) 2024 Antmicro
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Antmicro.Renode.Core.Structure.Registers;
|
||||
using Antmicro.Renode.Peripherals.Bus;
|
||||
using Antmicro.Renode.Utilities;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.IO;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace Antmicro.Renode.PeripheralsTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class SystemRDLGenTest
|
||||
{
|
||||
private class LoadedAssemblies
|
||||
{
|
||||
private LoadedAssemblies()
|
||||
{
|
||||
Assemblies = new Dictionary<string, AssemblyDefinition>();
|
||||
compiler = new AdHocCompiler();
|
||||
}
|
||||
|
||||
static public LoadedAssemblies Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if(instance == null)
|
||||
{
|
||||
instance = new LoadedAssemblies();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public IDictionary<string, AssemblyDefinition> Assemblies { get; set; }
|
||||
|
||||
public AssemblyDefinition LoadAssembly(RdlMeta meta)
|
||||
{
|
||||
var adhocPath = Path.Join(Assembly.GetExecutingAssembly().Location, "../../../../", meta.File);
|
||||
if(!Assemblies.TryGetValue(adhocPath, out var assembly))
|
||||
{
|
||||
var assemblyPath = compiler.Compile(new[] { adhocPath });
|
||||
assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
|
||||
Assemblies[adhocPath] = assembly;
|
||||
TypeManager.Instance.ScanFile(assemblyPath);
|
||||
}
|
||||
return assembly;
|
||||
}
|
||||
|
||||
private static LoadedAssemblies instance;
|
||||
private readonly AdHocCompiler compiler;
|
||||
}
|
||||
|
||||
static private IEnumerable<RdlMeta> GetTestCases
|
||||
{
|
||||
get
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
if(!assembly.TryFromResourceToTemporaryFile(SystemRDLResource, out var file))
|
||||
{
|
||||
Console.WriteLine($"Couldn't load the {SystemRDLResource} resource");
|
||||
yield break;
|
||||
}
|
||||
var fstream = File.OpenRead(file);
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
};
|
||||
options.Converters.Add(new TestFieldModeFlagConverter());
|
||||
|
||||
var metas = (List<RdlMeta>)JsonSerializer.Deserialize(fstream, typeof(List<RdlMeta>), options);
|
||||
|
||||
foreach(var meta in metas)
|
||||
{
|
||||
yield return meta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
var peripheralRegisterClasses = typeof(PeripheralRegister).GetTypeInfo().DeclaredNestedTypes.ToDictionary(ti => ti.Name);
|
||||
this.ValueRegisterField = peripheralRegisterClasses["ValueRegisterField"];
|
||||
Assert.NotNull(this.ValueRegisterField);
|
||||
this.FlagRegisterField = peripheralRegisterClasses["FlagRegisterField"];
|
||||
Assert.NotNull(this.FlagRegisterField);
|
||||
this.RegisterField = peripheralRegisterClasses["RegisterField"];
|
||||
Assert.NotNull(this.RegisterField);
|
||||
}
|
||||
|
||||
public class TestFieldModeFlagConverter : JsonConverter<FieldMode> {
|
||||
public override FieldMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
FieldMode flags = 0;
|
||||
|
||||
if(reader.TokenType != JsonTokenType.StartArray)
|
||||
{
|
||||
throw new JsonException("Expected a start of a flag array");
|
||||
}
|
||||
|
||||
while(reader.Read())
|
||||
{
|
||||
if(reader.TokenType == JsonTokenType.EndArray)
|
||||
{
|
||||
return flags;
|
||||
}
|
||||
|
||||
var flagStr = reader.GetString();
|
||||
foreach(var flag in Enum.GetValues(typeof(FieldMode)))
|
||||
{
|
||||
if(flagStr == flag.ToString())
|
||||
{
|
||||
flags |= (FieldMode)flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new JsonException("Unexpected end of stream");
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, FieldMode fieldMode, JsonSerializerOptions options)
|
||||
{
|
||||
throw new JsonException("Write not supported");
|
||||
}
|
||||
}
|
||||
|
||||
public class RdlField
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public uint Low { get; set; }
|
||||
public uint High { get; set; }
|
||||
public FieldMode Mode { get; set; }
|
||||
public string FieldType { get; set; }
|
||||
|
||||
public Type GetFieldType() =>
|
||||
Type.GetType("Antmicro.Renode.Core.Structure.Registers." + FieldType + ", Infrastructure");
|
||||
}
|
||||
|
||||
public class RdlRegister
|
||||
{
|
||||
public string ClassName { get; set; }
|
||||
public string InstanceName { get; set; }
|
||||
public ulong Offset { get; set; }
|
||||
public ulong ResetValue { get; set; }
|
||||
public List<RdlField> Fields { get; set; }
|
||||
}
|
||||
|
||||
public class RdlMeta
|
||||
{
|
||||
public string File { get; set; }
|
||||
public string Class { get; set; }
|
||||
public string RegisterContainerClass { get; set; }
|
||||
public List<RdlRegister> Registers { get; set; }
|
||||
|
||||
public Type GetClass()
|
||||
{
|
||||
return TypeManager.Instance.GetTypeByName(Class);
|
||||
}
|
||||
|
||||
public Type GetRegisterContainerClass() =>
|
||||
Type.GetType("Antmicro.Renode.Core.Structure.Registers." + RegisterContainerClass + ", Infrastructure");
|
||||
}
|
||||
|
||||
public List<RdlMeta> TestCases { get; set; }
|
||||
|
||||
private void InitPeripheral(RdlMeta meta)
|
||||
{
|
||||
if(meta.File != "")
|
||||
{
|
||||
var assembly = LoadedAssemblies.Instance.LoadAssembly(meta);
|
||||
Console.WriteLine("Loaded extra peripheral code, assembly: " + assembly.FullName);
|
||||
}
|
||||
pType = meta.GetClass();
|
||||
Assert.IsNotNull(pType);
|
||||
peripheral = (IDoubleWordPeripheral)Activator.CreateInstance(pType);
|
||||
Assert.IsNotNull(peripheral);
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(GetTestCases))]
|
||||
public void TestStaticMeta(RdlMeta meta)
|
||||
{
|
||||
Console.WriteLine("Checking structure of " + meta.Class + "...");
|
||||
InitPeripheral(meta);
|
||||
|
||||
var classes = pType.GetTypeInfo().DeclaredNestedTypes.ToDictionary(ti => ti.Name);
|
||||
var cFields = pType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.ToDictionary(fi => fi.Name);
|
||||
|
||||
Assert.AreEqual(meta.Registers.Count, classes.Count);
|
||||
|
||||
var registersCollection = pType.GetProperty("RegistersCollection");
|
||||
Assert.NotNull(registersCollection);
|
||||
Assert.AreEqual(meta.GetRegisterContainerClass(), registersCollection.PropertyType);
|
||||
|
||||
foreach(var register in meta.Registers)
|
||||
{
|
||||
Console.WriteLine("- Checking register " + register.InstanceName + "...");
|
||||
|
||||
|
||||
Assert.IsTrue(classes.ContainsKey(register.ClassName));
|
||||
Assert.IsTrue(cFields.ContainsKey(register.InstanceName));
|
||||
|
||||
var rClass = classes[register.ClassName];
|
||||
var rInstance = cFields[register.InstanceName];
|
||||
|
||||
Assert.AreEqual(rClass, rInstance.FieldType);
|
||||
|
||||
foreach(var field in register.Fields)
|
||||
{
|
||||
Console.WriteLine(" - Checking field " + register.InstanceName + "." + field.Name + "...");
|
||||
var rField = rClass.GetField(field.Name);
|
||||
Assert.IsNotNull(rField);
|
||||
Assert.AreEqual(field.GetFieldType(), rField.FieldType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(GetTestCases))]
|
||||
public void TestDynamicMeta(RdlMeta meta)
|
||||
{
|
||||
Console.WriteLine("Checking behavior of " + meta.Class + "...");
|
||||
|
||||
var getPrivateFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField;
|
||||
|
||||
InitPeripheral(meta);
|
||||
|
||||
foreach(var register in meta.Registers)
|
||||
{
|
||||
var regInst = pType.GetField(register.InstanceName, getPrivateFlags).GetValue(peripheral);
|
||||
|
||||
foreach(var field in register.Fields)
|
||||
{
|
||||
Console.WriteLine("Testing " + pType.ToString() + "." + register.InstanceName + "." + field.Name + "...");
|
||||
|
||||
var fieldField = regInst.GetType().GetField(field.Name, getPrivateFlags);
|
||||
Assert.AreEqual(field.GetFieldType(), fieldField.FieldType);
|
||||
|
||||
var fieldInst = fieldField.GetValue(regInst);
|
||||
|
||||
var fieldMode = this.RegisterField.GetField("FieldMode").GetValue(fieldInst);
|
||||
var position = this.RegisterField.GetField("Position").GetValue(fieldInst);
|
||||
var width = this.RegisterField.GetField("Width").GetValue(fieldInst);
|
||||
Assert.AreEqual(field.Mode, fieldMode);
|
||||
Assert.AreEqual(field.Low, position);
|
||||
Assert.AreEqual(field.High - field.Low + 1, width);
|
||||
|
||||
|
||||
|
||||
if(fieldInst.GetType() == this.ValueRegisterField)
|
||||
{
|
||||
TestFieldMode((IValueRegisterField)fieldInst, register.Offset, (int)field.Low, field.Mode);
|
||||
TestFieldReset((IValueRegisterField)fieldInst, (int)field.Low, register.ResetValue);
|
||||
}
|
||||
else if(fieldInst.GetType() == this.FlagRegisterField)
|
||||
{
|
||||
TestFieldMode((IFlagRegisterField)fieldInst, register.Offset, (int)field.Low, field.Mode);
|
||||
TestFieldReset((IFlagRegisterField)fieldInst, (int)field.Low, register.ResetValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Fail("Unhandled underlying field type: " + fieldInst.GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private IEnumerable<ulong> TestPattern(uint width = sizeof(ulong) * 8)
|
||||
{
|
||||
yield return 0;
|
||||
for(int i = 0; i < width; ++i)
|
||||
{
|
||||
yield return 1UL << i;
|
||||
}
|
||||
}
|
||||
|
||||
static private IEnumerable<ulong> TestPatternMask(uint width = sizeof(ulong) * 8)
|
||||
{
|
||||
ulong l = 0;
|
||||
for(int i = 0; i < width; i += 2)
|
||||
{
|
||||
l |= 1UL << i;
|
||||
}
|
||||
yield return l;
|
||||
yield return l << 1;
|
||||
}
|
||||
|
||||
private void TestFieldModeRead(IRegisterField<ulong> field, ulong offset, int low)
|
||||
{
|
||||
foreach(var pat in TestPattern((uint)field.Width))
|
||||
{
|
||||
ReportPattern(pat);
|
||||
|
||||
field.Value = pat;
|
||||
var read = peripheral.ReadDoubleWord((long)offset);
|
||||
Assert.AreEqual(pat, BitHelper.GetMaskedValue(read, low, field.Width) >> low);
|
||||
read = peripheral.ReadDoubleWord((long)offset); // Another check to ensure that the value was not modified by the read.
|
||||
Assert.AreEqual(pat, BitHelper.GetMaskedValue(read, low, field.Width) >> low);
|
||||
}
|
||||
}
|
||||
|
||||
private void TestFieldModeRead(IRegisterField<bool> field, ulong offset, int low)
|
||||
{
|
||||
field.Value = false;
|
||||
var read = peripheral.ReadDoubleWord((long)offset);
|
||||
Assert.AreEqual(0, BitHelper.GetMaskedValue(read, low, field.Width));
|
||||
read = peripheral.ReadDoubleWord((long)offset);
|
||||
Assert.AreEqual(0, BitHelper.GetMaskedValue(read, low, field.Width));
|
||||
|
||||
field.Value = true;
|
||||
read = peripheral.ReadDoubleWord((long)offset);
|
||||
Assert.AreEqual(1, BitHelper.GetMaskedValue(read, low, field.Width) >> low);
|
||||
read = peripheral.ReadDoubleWord((long)offset);
|
||||
Assert.AreEqual(1, BitHelper.GetMaskedValue(read, low, field.Width) >> low);
|
||||
}
|
||||
|
||||
private void TestFieldModeReadToClear(IRegisterField<ulong> field, ulong offset)
|
||||
{
|
||||
field.Value = BitHelper.GetMaskedValue(~0UL, 0, field.Width) ;
|
||||
peripheral.ReadDoubleWord((long)offset);
|
||||
Assert.AreEqual(0, field.Value);
|
||||
}
|
||||
|
||||
private void TestFieldModeReadToClear(IRegisterField<bool> field, ulong offset)
|
||||
{
|
||||
field.Value = false;
|
||||
peripheral.ReadDoubleWord((long)offset);
|
||||
Assert.AreEqual(false, field.Value);
|
||||
|
||||
field.Value = true;
|
||||
peripheral.ReadDoubleWord((long)offset);
|
||||
Assert.AreEqual(false, field.Value);
|
||||
}
|
||||
|
||||
private void TestFieldModeReadToSet(IRegisterField<ulong> field, ulong offset)
|
||||
{
|
||||
field.Value = 0UL;
|
||||
peripheral.ReadDoubleWord((long)offset);
|
||||
Assert.AreEqual(~0UL, field.Value);
|
||||
}
|
||||
|
||||
private void TestFieldModeReadToSet(IRegisterField<bool> field, ulong offset)
|
||||
{
|
||||
field.Value = false;
|
||||
peripheral.ReadDoubleWord((long)offset);
|
||||
Assert.AreEqual(true, field.Value);
|
||||
|
||||
field.Value = true;
|
||||
peripheral.ReadDoubleWord((long)offset);
|
||||
Assert.AreEqual(true, field.Value);
|
||||
}
|
||||
|
||||
private void TestFieldModeWrite(IRegisterField<ulong> field, ulong offset, int low, bool negate = false)
|
||||
{
|
||||
foreach(var pat in TestPattern((uint)field.Width))
|
||||
{
|
||||
ReportPattern(pat);
|
||||
|
||||
var v = (negate ? ~(uint)pat : (uint)pat) << low;
|
||||
peripheral.WriteDoubleWord((long)offset, v);
|
||||
Assert.AreEqual(pat, field.Value);
|
||||
peripheral.WriteDoubleWord((long)offset, v);
|
||||
Assert.AreEqual(pat, field.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void TestFieldModeWrite(IRegisterField<bool> field, ulong offset, int low, bool negate = false)
|
||||
{
|
||||
field.Value = false;
|
||||
peripheral.WriteDoubleWord((long)offset, negate ? (1U << low) : 0U);
|
||||
Assert.AreEqual(false, field.Value);
|
||||
|
||||
peripheral.WriteDoubleWord((long)offset, negate ? 0 : (1U << low));
|
||||
Assert.AreEqual(true, field.Value);
|
||||
peripheral.WriteDoubleWord((long)offset, negate ? 0 : (1U << low));
|
||||
Assert.AreEqual(true, field.Value);
|
||||
}
|
||||
|
||||
private void TestFieldModeSet(IRegisterField<ulong> field, ulong offset, int low, bool negate = false)
|
||||
{
|
||||
foreach(var mask in TestPatternMask((uint)field.Width))
|
||||
{
|
||||
foreach(var pat in TestPattern((uint)field.Width))
|
||||
{
|
||||
ReportMaskAndPattern(mask, pat);
|
||||
|
||||
var v = (negate ? ~(uint)pat : (uint)pat) << low;
|
||||
field.Value = mask;
|
||||
peripheral.WriteDoubleWord((long)offset, v);
|
||||
Assert.AreEqual(mask | pat, field.Value);
|
||||
peripheral.WriteDoubleWord((long)offset, v);
|
||||
Assert.AreEqual(mask | pat, field.Value);
|
||||
peripheral.WriteDoubleWord((long)offset, 0U);
|
||||
Assert.AreEqual(mask | pat, field.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TestFieldModeSet(IRegisterField<bool> field, ulong offset, int low, bool negate = false)
|
||||
{
|
||||
field.Value = false;
|
||||
peripheral.WriteDoubleWord((long)offset, negate ? (1U << low) : 0U);
|
||||
Assert.AreEqual(false, field.Value);
|
||||
|
||||
peripheral.WriteDoubleWord((long)offset, negate ? 0 : (1U << low));
|
||||
Assert.AreEqual(true, field.Value);
|
||||
peripheral.WriteDoubleWord((long)offset, negate ? 0 : (1U << low));
|
||||
Assert.AreEqual(true, field.Value);
|
||||
peripheral.WriteDoubleWord((long)offset, negate ? (1U << low) : 0);
|
||||
Assert.AreEqual(true, field.Value);
|
||||
}
|
||||
|
||||
private void TestFieldModeToggle(IRegisterField<ulong> field, ulong offset, int low, bool negate = false)
|
||||
{
|
||||
foreach(var mask in TestPatternMask((uint)field.Width))
|
||||
{
|
||||
field.Value = mask;
|
||||
foreach(var pat in TestPattern((uint)field.Width))
|
||||
{
|
||||
ReportMaskAndPattern(mask, pat);
|
||||
|
||||
var v = (negate ? ~(uint)pat : (uint)pat) << low;
|
||||
peripheral.WriteDoubleWord((long)offset, v);
|
||||
Assert.AreEqual((mask | pat) & ~(mask & pat), field.Value);
|
||||
peripheral.WriteDoubleWord((long)offset, v);
|
||||
Assert.AreEqual(mask, field.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TestFieldModeToggle(IRegisterField<bool> field, ulong offset, int low, bool negate = false)
|
||||
{
|
||||
field.Value = false;
|
||||
peripheral.WriteDoubleWord((long)offset, negate ? (1U << low) : 0U);
|
||||
Assert.AreEqual(false, field.Value);
|
||||
|
||||
peripheral.WriteDoubleWord((long)offset, negate ? 0 : (1U << low));
|
||||
Assert.AreEqual(true, field.Value);
|
||||
peripheral.WriteDoubleWord((long)offset, negate ? (1U << low) : 0U);
|
||||
Assert.AreEqual(true, field.Value);
|
||||
|
||||
peripheral.WriteDoubleWord((long)offset, negate ? 0 : (1U << low));
|
||||
Assert.AreEqual(false, field.Value);
|
||||
}
|
||||
|
||||
private void TestFieldModeWriteOneToClear(IRegisterField<ulong> field, ulong offset, int low, bool negate = false)
|
||||
{
|
||||
foreach(var mask in TestPatternMask((uint)field.Width))
|
||||
{
|
||||
foreach(var pat in TestPattern((uint)field.Width))
|
||||
{
|
||||
ReportMaskAndPattern(mask, pat);
|
||||
|
||||
var v = (negate ? ~(uint)pat : (uint)pat) << low;
|
||||
field.Value = mask;
|
||||
peripheral.WriteDoubleWord((long)offset, v);
|
||||
Assert.AreEqual(mask & ~pat, field.Value);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void TestFieldModeWriteOneToClear(IRegisterField<bool> field, ulong offset, int low, bool negate = false)
|
||||
{
|
||||
field.Value = true;
|
||||
peripheral.WriteDoubleWord((long)offset, negate ? (1U << low) : 0U);
|
||||
Assert.AreEqual(true, field.Value);
|
||||
|
||||
peripheral.WriteDoubleWord((long)offset, negate ? 0U : (1U << low));
|
||||
Assert.AreEqual(false, field.Value);
|
||||
|
||||
peripheral.WriteDoubleWord((long)offset, negate ? 0U : (1U << low));
|
||||
Assert.AreEqual(false, field.Value);
|
||||
}
|
||||
|
||||
private static void ReportPattern(ulong pat)
|
||||
{
|
||||
Console.WriteLine(" - Test pattern: " + FormatBits(pat));
|
||||
}
|
||||
|
||||
private static void ReportMaskAndPattern(ulong mask, ulong pat)
|
||||
{
|
||||
Console.WriteLine(" - Test Mask: " + FormatBits(mask));
|
||||
Console.WriteLine(" Test pattern: " + FormatBits(pat));
|
||||
}
|
||||
|
||||
private void TestFieldMode(IRegisterField<ulong> field, ulong offset, int low, FieldMode mode)
|
||||
{
|
||||
if(FieldModeHelper.ReadBits(mode) != 0)
|
||||
{
|
||||
Console.WriteLine(" Performing read test...");
|
||||
}
|
||||
switch(FieldModeHelper.ReadBits(mode))
|
||||
{
|
||||
case FieldMode.Read: TestFieldModeRead(field, offset, low); break;
|
||||
case FieldMode.ReadToClear: TestFieldModeReadToClear(field, offset); break;
|
||||
case FieldMode.ReadToSet: TestFieldModeReadToSet(field, offset); break;
|
||||
}
|
||||
|
||||
if(FieldModeHelper.WriteBits(mode) != 0)
|
||||
{
|
||||
Console.WriteLine(" Performing write test...");
|
||||
}
|
||||
switch(FieldModeHelper.WriteBits(mode))
|
||||
{
|
||||
case FieldMode.Write: TestFieldModeWrite(field, offset, low); break;
|
||||
case FieldMode.Set: TestFieldModeSet(field, offset, low); break;
|
||||
case FieldMode.Toggle: TestFieldModeToggle(field, offset, low); break;
|
||||
case FieldMode.WriteOneToClear: TestFieldModeWriteOneToClear(field, offset, low); break;
|
||||
case FieldMode.WriteZeroToClear: TestFieldModeWriteOneToClear(field, offset, low, negate: true); break;
|
||||
case FieldMode.WriteZeroToSet: TestFieldModeSet(field, offset, low, negate: true); break;
|
||||
case FieldMode.WriteZeroToToggle: TestFieldModeToggle(field, offset, low, negate: true); break;
|
||||
}
|
||||
}
|
||||
|
||||
private void TestFieldMode(IRegisterField<bool> field, ulong offset, int low, FieldMode mode)
|
||||
{
|
||||
if(FieldModeHelper.ReadBits(mode) != 0)
|
||||
{
|
||||
Console.WriteLine(" Performing read test...");
|
||||
}
|
||||
switch(FieldModeHelper.ReadBits(mode))
|
||||
{
|
||||
case FieldMode.Read: TestFieldModeRead(field, offset, low); break;
|
||||
case FieldMode.ReadToClear: TestFieldModeReadToClear(field, offset); break;
|
||||
case FieldMode.ReadToSet: TestFieldModeReadToSet(field, offset); break;
|
||||
}
|
||||
|
||||
if(FieldModeHelper.WriteBits(mode) != 0)
|
||||
{
|
||||
Console.WriteLine(" Performing write test...");
|
||||
}
|
||||
switch(FieldModeHelper.WriteBits(mode))
|
||||
{
|
||||
case FieldMode.Write: TestFieldModeWrite(field, offset, low); break;
|
||||
case FieldMode.Set: TestFieldModeSet(field, offset, low); break;
|
||||
case FieldMode.Toggle: TestFieldModeToggle(field, offset, low); break;
|
||||
case FieldMode.WriteOneToClear: TestFieldModeWriteOneToClear(field, offset, low); break;
|
||||
case FieldMode.WriteZeroToClear: TestFieldModeWriteOneToClear(field, offset, low, negate: true); break;
|
||||
case FieldMode.WriteZeroToSet: TestFieldModeSet(field, offset, low, negate: true); break;
|
||||
case FieldMode.WriteZeroToToggle: TestFieldModeToggle(field, offset, low, negate: true); break;
|
||||
}
|
||||
}
|
||||
|
||||
void TestFieldReset(IRegisterField<ulong> field, int low, ulong registerResetValue)
|
||||
{
|
||||
var expect = registerResetValue >> low;
|
||||
field.Value = BitHelper.GetMaskedValue(~expect, 0, field.Width);
|
||||
|
||||
var registers = (peripheral as IProvidesRegisterCollection<DoubleWordRegisterCollection>).RegistersCollection;
|
||||
registers.Reset();
|
||||
|
||||
Assert.AreEqual(field.Value, expect);
|
||||
}
|
||||
|
||||
void TestFieldReset(IRegisterField<bool> field, int low, ulong registerResetValue)
|
||||
{
|
||||
var expect = BitHelper.GetMaskedValue(registerResetValue >> low, 0, 1) == 1;
|
||||
field.Value = !expect;
|
||||
|
||||
var registers = (peripheral as IProvidesRegisterCollection<DoubleWordRegisterCollection>).RegistersCollection;
|
||||
registers.Reset();
|
||||
|
||||
Assert.AreEqual(field.Value, expect);
|
||||
}
|
||||
|
||||
// Older .NET versions do not support "b" format specifier
|
||||
static string FormatBits(ulong value)
|
||||
{
|
||||
string res = "";
|
||||
foreach(var bit in BitHelper.GetBits(value).Reverse())
|
||||
{
|
||||
res += bit ? '1' : '0';
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private IDoubleWordPeripheral peripheral;
|
||||
private Type pType;
|
||||
|
||||
private Type ValueRegisterField;
|
||||
private Type FlagRegisterField;
|
||||
private Type RegisterField;
|
||||
|
||||
private const string SystemRDLResource = "Antmicro.Renode.PeripheralsTests.SystemRDLJson";
|
||||
}
|
||||
}
|
||||
143
tools/PeakRDL-renode/tests/SystemRDLMem1PeripheralTest.cs
Normal file
143
tools/PeakRDL-renode/tests/SystemRDLMem1PeripheralTest.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
// Copyright (C) 2024 Antmicro
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Antmicro.Renode.Peripherals.Bus;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
namespace Antmicro.Renode.PeripheralsTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class SystemRDLMem1PeripheralTest
|
||||
{
|
||||
public struct TestCase {
|
||||
public bool FLAG1 { get; set; }
|
||||
public bool FLAG2 { get; set; }
|
||||
public byte VALUE1 { get; set; }
|
||||
public uint VALUE2 { get; set; }
|
||||
public uint Memory { get; set; }
|
||||
}
|
||||
|
||||
static public List<TestCase> testCases = new List<TestCase> {
|
||||
new TestCase {
|
||||
FLAG1 = false,
|
||||
FLAG2 = false,
|
||||
VALUE1 = 0x0,
|
||||
VALUE2 = 0x0,
|
||||
Memory = 0x0000_0000,
|
||||
},
|
||||
new TestCase {
|
||||
FLAG1 = true,
|
||||
FLAG2 = false,
|
||||
VALUE1 = 0x0,
|
||||
VALUE2 = 0x400,
|
||||
Memory = 0x0001_0001,
|
||||
},
|
||||
new TestCase {
|
||||
FLAG1 = true,
|
||||
FLAG2 = true,
|
||||
VALUE1 = 0xf,
|
||||
VALUE2 = 0x216400,
|
||||
Memory = 0x0859_003f,
|
||||
},
|
||||
new TestCase {
|
||||
FLAG1 = true,
|
||||
FLAG2 = true,
|
||||
VALUE1 = 0xf,
|
||||
VALUE2 = 0x859,
|
||||
Memory = 0x0002_167f,
|
||||
},
|
||||
new TestCase {
|
||||
FLAG1 = false,
|
||||
FLAG2 = true,
|
||||
VALUE1 = 0xf,
|
||||
VALUE2 = 0x1ff_ffff,
|
||||
Memory = 0xffff_fffe,
|
||||
},
|
||||
};
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
peripheral = new Peripherals.Mocks.Mem1Peripheral();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSize()
|
||||
{
|
||||
Assert.AreEqual(16, peripheral.Mem1.Size);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBounds()
|
||||
{
|
||||
var _ = peripheral.Mem1[mem1ElementCount - 1];
|
||||
try
|
||||
{
|
||||
_ = peripheral.Mem1[mem1ElementCount];
|
||||
}
|
||||
catch(IndexOutOfRangeException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Assert.Fail("Out-of-bounds memory access succeeded");
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(testCases))]
|
||||
public void TestRead(TestCase testCase)
|
||||
{
|
||||
for(long i = 0; i < peripheral.Mem1.Size / 4; ++i)
|
||||
{
|
||||
(peripheral as IDoubleWordPeripheral)
|
||||
.WriteDoubleWord(mem1Offset + i * 4, testCase.Memory);
|
||||
}
|
||||
|
||||
for(long i = 0; i < peripheral.Mem1.Size / mem1ElementCount; ++i)
|
||||
{
|
||||
Assert.AreEqual(testCase.FLAG1, peripheral.Mem1[i].FLAG1);
|
||||
Assert.AreEqual(testCase.FLAG2, peripheral.Mem1[i].FLAG2);
|
||||
Assert.AreEqual(testCase.VALUE1, peripheral.Mem1[i].VALUE1);
|
||||
Assert.AreEqual(testCase.VALUE2, peripheral.Mem1[i].VALUE2);
|
||||
}
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(nameof(testCases))]
|
||||
public void TestWrite(TestCase testCase)
|
||||
{
|
||||
for(long i = 0; i < peripheral.Mem1.Size / mem1ElementCount; ++i)
|
||||
{
|
||||
peripheral.Mem1[i].FLAG1 = testCase.FLAG1;
|
||||
peripheral.Mem1[i].FLAG2 = testCase.FLAG2;
|
||||
peripheral.Mem1[i].VALUE1 = testCase.VALUE1;
|
||||
peripheral.Mem1[i].VALUE2 = testCase.VALUE2;
|
||||
}
|
||||
|
||||
for(long i = 0; i < peripheral.Mem1.Size / 4; ++i)
|
||||
{
|
||||
var word = (peripheral as IDoubleWordPeripheral)
|
||||
.ReadDoubleWord(mem1Offset + i * 4);
|
||||
Assert.AreEqual(testCase.Memory & mem1RegMask, word & mem1RegMask);
|
||||
}
|
||||
}
|
||||
|
||||
private Peripherals.Mocks.Mem1Peripheral peripheral;
|
||||
long mem1Offset = 0x10;
|
||||
long mem1ElementCount = 4;
|
||||
long mem1RegMask = 0x3fff_ffff;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<Project DefaultTargets="Build" Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks Condition="$(OS) != 'Windows_NT'">net6.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition="$(OS) == 'Windows_NT'">net6.0-windows10.0.17763.0</TargetFrameworks>
|
||||
<AssemblyName>PeripheralsTests</AssemblyName>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moq" Version="4.18.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Infrastructure\src\Infrastructure_NET.csproj"/>
|
||||
<ProjectReference Include="..\..\..\lib\Migrant\Migrant\Migrant_NET.csproj"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="generated\**"/>
|
||||
<Compile Include="generated\Mem1Peripheral.cs"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="systemrdl.json">
|
||||
<LogicalName>Antmicro.Renode.PeripheralsTests.SystemRDLJson</LogicalName>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
46
tools/PeakRDL-renode/tests/generate_cs.py
Executable file
46
tools/PeakRDL-renode/tests/generate_cs.py
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2024 Antmicro
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from argparse import ArgumentParser
|
||||
import json
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument('json', type=str, help='JSON file with test info')
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.json, 'r') as json_file:
|
||||
test_desc = json.loads(json_file.read())
|
||||
|
||||
for test in test_desc:
|
||||
output = test["file"]
|
||||
dir = os.path.dirname(output)
|
||||
if not os.path.exists(dir):
|
||||
print(f"Creating the `{dir}` directory")
|
||||
os.makedirs(dir)
|
||||
print(f'Generating `{output}`...')
|
||||
subprocess.run(test['peakrdl'])
|
||||
|
||||
print('Done!')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
3
tools/PeakRDL-renode/tests/prepare_memory_tests.sh
Executable file
3
tools/PeakRDL-renode/tests/prepare_memory_tests.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
peakrdl renode rdl/mem1.rdl -N Mocks -n Mem1Peripheral -o generated/Mem1Peripheral.cs --all-public
|
||||
26
tools/PeakRDL-renode/tests/rdl/mem1.rdl
Normal file
26
tools/PeakRDL-renode/tests/rdl/mem1.rdl
Normal file
@@ -0,0 +1,26 @@
|
||||
addrmap Device {
|
||||
mem {
|
||||
mementries = 4;
|
||||
memwidth = 64;
|
||||
sw = rw;
|
||||
reg {
|
||||
regwidth = 32;
|
||||
field {
|
||||
name = "FLAG1";
|
||||
sw = rw;
|
||||
} flag1 [0:0];
|
||||
field {
|
||||
name = "FLAG2";
|
||||
sw = rw;
|
||||
} flag2 [1:1];
|
||||
field {
|
||||
name = "VALUE1";
|
||||
sw = rw;
|
||||
} value1 [5:2];
|
||||
field {
|
||||
name = "VALUE2";
|
||||
sw = rw;
|
||||
} value2 [30:6];
|
||||
} structure [4];
|
||||
} external mem1 @ 0x10;
|
||||
};
|
||||
31
tools/PeakRDL-renode/tests/rdl/test1.rdl
Normal file
31
tools/PeakRDL-renode/tests/rdl/test1.rdl
Normal file
@@ -0,0 +1,31 @@
|
||||
addrmap Peripheral {
|
||||
regfile {
|
||||
default regwidth = 32;
|
||||
|
||||
reg {
|
||||
name = "Fields";
|
||||
field {
|
||||
name = "FIRST";
|
||||
sw = r;
|
||||
hw = w;
|
||||
} first [7:0];
|
||||
field {
|
||||
name = "SECOND";
|
||||
sw = w;
|
||||
hw = r;
|
||||
} second [15:8];
|
||||
field {
|
||||
name = "THIRD";
|
||||
sw = rw;
|
||||
hw = rw;
|
||||
onwrite = woclr;
|
||||
} third [23:16];
|
||||
field {
|
||||
name = "FOURTH";
|
||||
sw = rw;
|
||||
hw = rw;
|
||||
onread = rclr;
|
||||
} fourth [31:24];
|
||||
} fields @ 0x0;
|
||||
} registers @ 0x0;
|
||||
};
|
||||
20
tools/PeakRDL-renode/tests/rdl/test2.rdl
Normal file
20
tools/PeakRDL-renode/tests/rdl/test2.rdl
Normal file
@@ -0,0 +1,20 @@
|
||||
addrmap Peripheral {
|
||||
regfile {
|
||||
default regwidth = 32;
|
||||
|
||||
reg {
|
||||
name = "BitFields";
|
||||
field {
|
||||
name = "FIRST";
|
||||
sw = r;
|
||||
hw = w;
|
||||
} first [0:0];
|
||||
field {
|
||||
name = "SECOND";
|
||||
sw = rw;
|
||||
hw = rw;
|
||||
onwrite = wzc;
|
||||
} second [1:1];
|
||||
} bit_fields @ 0x0;
|
||||
} registers @ 0x100;
|
||||
};
|
||||
25
tools/PeakRDL-renode/tests/rdl/test3.rdl
Normal file
25
tools/PeakRDL-renode/tests/rdl/test3.rdl
Normal file
@@ -0,0 +1,25 @@
|
||||
addrmap Peripheral {
|
||||
regfile {
|
||||
default regwidth = 32;
|
||||
|
||||
reg {
|
||||
name = "COOL_REGISTER";
|
||||
field {
|
||||
name = "COOL_FIELD";
|
||||
sw = w;
|
||||
hw = rw;
|
||||
woset = true;
|
||||
} cool_field [31:0];
|
||||
} cool_register @ 0x0;
|
||||
reg {
|
||||
name = "UNCOOL_REGISTER";
|
||||
field {
|
||||
name = "UNCOOL_FIELD";
|
||||
sw = rw;
|
||||
hw = rw;
|
||||
onwrite = wzt;
|
||||
reset = 1;
|
||||
} uncool_field [31:0];
|
||||
} uncool_register @ 0x4;
|
||||
} registers @ 0x100;
|
||||
};
|
||||
17
tools/PeakRDL-renode/tests/rdl/test4.rdl
Normal file
17
tools/PeakRDL-renode/tests/rdl/test4.rdl
Normal file
@@ -0,0 +1,17 @@
|
||||
addrmap Peripheral {
|
||||
default regwidth = 32;
|
||||
|
||||
reg {
|
||||
field {
|
||||
name = "FIRST";
|
||||
sw = r;
|
||||
} first [7:0];
|
||||
} reg1 @ 0x0;
|
||||
|
||||
reg {
|
||||
field {
|
||||
name = "SECOND";
|
||||
sw = w;
|
||||
} second [7:0];
|
||||
} reg2 @ 0x0;
|
||||
};
|
||||
137
tools/PeakRDL-renode/tests/systemrdl.json
Normal file
137
tools/PeakRDL-renode/tests/systemrdl.json
Normal file
@@ -0,0 +1,137 @@
|
||||
[
|
||||
{
|
||||
"peakrdl": ["peakrdl", "renode", "-N", "Mocks", "-n", "SystemRDLTest1", "-o", "generated/SystemRDLTest1.cs", "rdl/test1.rdl"],
|
||||
"file": "generated/SystemRDLTest1.cs",
|
||||
"class": "Antmicro.Renode.Peripherals.Mocks.SystemRDLTest1",
|
||||
"registerContainerClass": "DoubleWordRegisterCollection",
|
||||
"registers": [
|
||||
{
|
||||
"className": "Registers_FieldsType",
|
||||
"instanceName": "Registers_Fields",
|
||||
"offset": 0,
|
||||
"resetValue": 0,
|
||||
"fields": [
|
||||
{
|
||||
"name": "FIRST",
|
||||
"low": 0,
|
||||
"high": 7,
|
||||
"mode": ["Read"],
|
||||
"fieldType": "IValueRegisterField"
|
||||
},
|
||||
{
|
||||
"name": "SECOND",
|
||||
"low": 8,
|
||||
"high": 15,
|
||||
"mode": ["Write"],
|
||||
"fieldType": "IValueRegisterField"
|
||||
},
|
||||
{
|
||||
"name": "THIRD",
|
||||
"low": 16,
|
||||
"high": 23,
|
||||
"mode": ["Read", "WriteOneToClear"],
|
||||
"fieldType": "IValueRegisterField"
|
||||
},
|
||||
{
|
||||
"name": "FOURTH",
|
||||
"low": 24,
|
||||
"high": 31,
|
||||
"mode": ["ReadToClear", "Write"],
|
||||
"fieldType": "IValueRegisterField"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"peakrdl": ["peakrdl", "renode", "-N", "Mocks", "-n", "SystemRDLTest2", "-o", "generated/SystemRDLTest2.cs", "rdl/test2.rdl"],
|
||||
"file": "generated/SystemRDLTest2.cs",
|
||||
"class": "Antmicro.Renode.Peripherals.Mocks.SystemRDLTest2",
|
||||
"registerContainerClass": "DoubleWordRegisterCollection",
|
||||
"registers": [
|
||||
{
|
||||
"className": "Registers_BitFieldsType",
|
||||
"instanceName": "Registers_BitFields",
|
||||
"offset": 256,
|
||||
"resetValue": 0,
|
||||
"fields": [
|
||||
{
|
||||
"name": "FIRST",
|
||||
"low": 0,
|
||||
"high": 0,
|
||||
"mode": ["Read"],
|
||||
"fieldType": "IFlagRegisterField"
|
||||
},
|
||||
{
|
||||
"name": "SECOND",
|
||||
"low": 1,
|
||||
"high": 1,
|
||||
"mode": ["Read", "WriteZeroToClear"],
|
||||
"fieldType": "IFlagRegisterField"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"peakrdl": ["peakrdl", "renode", "-N", "Mocks", "-n", "SystemRDLTest3", "-o", "generated/SystemRDLTest3.cs", "rdl/test3.rdl"],
|
||||
"file": "generated/SystemRDLTest3.cs",
|
||||
"class": "Antmicro.Renode.Peripherals.Mocks.SystemRDLTest3",
|
||||
"registerContainerClass": "DoubleWordRegisterCollection",
|
||||
"registers": [
|
||||
{
|
||||
"className": "Registers_CoolRegisterType",
|
||||
"instanceName": "Registers_CoolRegister",
|
||||
"offset": 256,
|
||||
"resetValue": 0,
|
||||
"fields": [
|
||||
{
|
||||
"name": "COOL_FIELD",
|
||||
"low": 0,
|
||||
"high": 31,
|
||||
"mode": ["Set"],
|
||||
"fieldType": "IValueRegisterField"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"className": "Registers_UncoolRegisterType",
|
||||
"instanceName": "Registers_UncoolRegister",
|
||||
"offset": 260,
|
||||
"resetValue": 1,
|
||||
"fields": [
|
||||
{
|
||||
"name": "UNCOOL_FIELD",
|
||||
"low": 0,
|
||||
"high": 31,
|
||||
"mode": ["Read", "WriteZeroToToggle"],
|
||||
"fieldType": "IValueRegisterField"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"peakrdl": ["peakrdl", "renode", "-N", "Mocks", "-n", "SystemRDLTest4", "-o", "generated/SystemRDLTest4.cs", "rdl/test4.rdl"],
|
||||
"file": "generated/SystemRDLTest4.cs",
|
||||
"class": "Antmicro.Renode.Peripherals.Mocks.SystemRDLTest4",
|
||||
"registerContainerClass": "DoubleWordRegisterCollection",
|
||||
"registers": [
|
||||
{
|
||||
"className": "Reg1Reg2Type",
|
||||
"instanceName": "Reg1Reg2",
|
||||
"offset": 0,
|
||||
"resetValue": 0,
|
||||
"fields": [
|
||||
{
|
||||
"name": "FIRST_SECOND",
|
||||
"low": 0,
|
||||
"high": 7,
|
||||
"mode": ["Read", "Write"],
|
||||
"fieldType": "IValueRegisterField"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user