Fjv.xCPU
0.1.0
dotnet add package Fjv.xCPU --version 0.1.0
NuGet\Install-Package Fjv.xCPU -Version 0.1.0
<PackageReference Include="Fjv.xCPU" Version="0.1.0" />
<PackageVersion Include="Fjv.xCPU" Version="0.1.0" />
<PackageReference Include="Fjv.xCPU" />
paket add Fjv.xCPU --version 0.1.0
#r "nuget: Fjv.xCPU, 0.1.0"
#:package Fjv.xCPU@0.1.0
#addin nuget:?package=Fjv.xCPU&version=0.1.0
#tool nuget:?package=Fjv.xCPU&version=0.1.0
General
Fjv.xCPU is an experimental Assembly compiler library that provides architecture and methods to customize your own cpu compiler.
Getting started
How to define instructions
You can define CPU and Assembly instruction and its logic using this library.
CPU instruction definition sample
You need to select a CPU to define it. For this example we will use a Z84. This is a vintage CPU, and is perfect to teach how use this library. Even in the sources you can see a extend sample about that.
You can donwload the chip datasheet from https://pdf1.alldatasheet.com/datasheet-pdf/view/600133/ZILOG/Z84C00.html
The Z84C00 CPU has a 8 BIT LOAD GROUP. You can see its two first instructions:
- LD r, r' has the opcode "01 r r'" where the base value is 01000000 (0x40 in hexadecimal). You must put the left operand (register) at third position, and the right operand (prime register) at zero position of the byte.
- LD r, n has the opcode "00 r 110" where the value is 00000110 (0x06 in hexadecimal). You must put the lef operand (register) at the third position again and the right operand (byte) in a new byte.
Example code:
//removed code for brevity.
// 8 bit load group.
var mnemonic = "ld";
group.AddInstruction(new CpuInstructionType() {
// indicate the the command mnemonic.
Mnemonic = mnemonic,
// set the types of operands supported.
Operand = new Type[] { typeof(Z80Register), typeof(Z80PrimeRegister) },
// the large in bytes of the instruction result.
Size = new Func<IInstructionParam, int>((x) => { return 1; }),
// custom compute instruction.
Instruction = new Func<IInstructionParam, byte[]>((x) => {
// ld r r' (01 rrr r'r'r')
byte hex = 0x40;
// take register addresses.
var left = (byte)((Z80Register)x.LeftValue).Address;
var right = (byte)((Z80PrimeRegister)x.RightValue).Address;
// calculation of the byte.
left = (byte)(left << 3);
hex = (byte)(hex | left | right);
// return the result.
return new byte[1] { hex };
})
}).AddInstruction(new CpuInstructionType() {
Mnemonic = mnemonic,
Operand = new Type[] { typeof(Z80Register), typeof(byte) },
Size = new Func<IInstructionParam, int>((x) => { return 2; }),
Instruction = new Func<IInstructionParam, byte[]>((x) => {
// ld r, n (00 rrr 110)
byte hex = 0x06;
// take register and value.
var register = (Z80Register)x.LeftValue;
var right = (byte)x.RightValue;
// calculation of the byte.
var left = (byte)(register.Address << 3);
hex = (byte)(hex | left);
// return array of two bytes.
return new byte[2] { hex, right };
})
})
The CPU instructions result always be an array of bytes.
As you can see, we put logic into Instruction property to process the data. The operands can be customized as Z80Register or predefined like byte, Uint16, string, etc. The library resolve wich instruction must be use to process the line of assembly code and wich types match with handling them. You don't need put code to read or recognize structured text in your C# code.
In this example Z80Register represent a register in the Z80 CPU, and they are: a, b, c, and others. In our code, we define it something like this:
//removed code for brevity.
public class Z80Register : RegisterType
{
public static Z80Register B = new Z80Register()
{
Mnemonic = "b",
Address = 0x00
};
public static Z80Register C = new Z80Register()
{
Mnemonic = "c",
Address = 0x01
};
//removed code for brevity.
}
Following this pattern allow to the library recognize our custom operands like registers, prime registers, index registers, and so.
Assembly language definition sample
The mode to define the language is very similar with CPU definition, the difference resides in the return type that is AssemblyDataTypeResult, because the languaje has more functions to do.
The code below shows us how we would define the EQU command for assembly languaje.
//equ equate
Instructions.Add(new AssemblyInstructionType()
{
Mnemonic = "equ",
Operand = new Type[] { typeof(UInt16) },
Instruction = new Func<IInstructionArgument, AssemblyDataTypeResult[]>((x) => {
var value = x.Left;
return new AssemblyDataTypeResult[] {
new AssemblyDataTypeResult {
Object = Bytes.GetUint16(value),
DataType = DataType.Information
}
};
})
});
The definition is used to tell to the compiler service what process to do by DataType property and passing the data throught the Object property.
You must passing a type of value to Object property for each DataType:
- DataType.Data: byte[] (array of bytes). Can be code for the CPU model.
- DataType.Code: string[] (array of strings). Would be lines of assembly codes.
- DataType.Information: int (integer value). It is a information about index.
The other enum is Internal, but it is not implemeted yet inside the SinglePassCompiler.
The next sample shows how pattern by RegexPattern to recognize special strings on the command. In this case take another source file from folder or a remote resource to compile and put it inside your program.
//ext "<external or remote file>"
Instructions.Add(new AssemblyInstructionType()
{
Mnemonic = "ext",
RegexPattern = "\"(.*?)\"",
Instruction = new Func<IInstructionArgument, AssemblyDataTypeResult[]>((x) => {
var source = x.Left?.Replace("\"", "");
var sourcepath = x.Source;
Uri.TryCreate(source, UriKind.Absolute, out Uri uri);
var file = System.IO.Path.Combine(sourcepath, source);
if (System.IO.File.Exists(source))
{
return new AssemblyDataTypeResult[] {
new AssemblyDataTypeResult {
Object = System.IO.File.ReadAllLines(source).ToArray(),
DataType = DataType.Code
}
};
}
else if (System.IO.File.Exists(file))
{
return new AssemblyDataTypeResult[] {
new AssemblyDataTypeResult {
Object = System.IO.File.ReadAllLines(file).ToArray(),
DataType = DataType.Code
}
};
}
else if (uri.IsAbsoluteUri)
{
OnBeginLongProcess?.Invoke(this, $"Load {uri.AbsoluteUri}...");
var client = new System.Net.WebClient();
string downloadString = client.DownloadString(uri);
var temp = System.IO.Path.GetTempFileName();
System.IO.File.WriteAllText(temp, downloadString);
OnEndLongProcess?.Invoke(this, $"{uri.AbsoluteUri} ok.");
return new AssemblyDataTypeResult[] {
new AssemblyDataTypeResult {
Object = System.IO.File.ReadAllLines(temp),
DataType = DataType.Code
}
};
}
else
{
throw new Exception($"{file} does not exist.");
}
})
});
You can see more about this inside Z80Assembly sample project.
SinglePassCompiler
The SinglePassCompiler is a class that define a sealed single pass compiler. It is an implementation about a compiler that only passing once time throughout source code. All the labels are saved or resolved on the way.
The constructor require two parameters:
- IInstructionResolverProvider<byte>, for cpu instruction set.
- IInstructionResolverProvider<AssemblyDataTypeResult>, for assembly instruction set.
IProvider
IProvider is an interface that offering a method to get an instance of InstructionResolverProvider class which is an abstraction layer to use with a compiler. I recomend use it inside your instruction library that correspond.
The code below shows how implement this interface.
public class Provider : IProvider<CpuInstructionType, byte>
{
public InstructionResolverProvider<CpuInstructionType, byte> GetInstructionResolverProvider()
{
return new InstructionResolverProvider<CpuInstructionType, byte>(new InstructionResolver<CpuInstructionType, byte>(new MyCpuInstructionSet()));
}
}
MyCpuInstructionSet is the class name that you must change when implement this code snippet for your cpu instruction library.
public class Provider : IProvider<AssemblyInstructionType, AssemblyDataTypeResult>
{
public InstructionResolverProvider<AssemblyInstructionType, AssemblyDataTypeResult> GetInstructionResolverProvider()
{
return new InstructionResolverProvider<AssemblyInstructionType, AssemblyDataTypeResult> (new InstructionResolver<AssemblyInstructionType, AssemblyDataTypeResult>(new MyAssemblyInstructionSet()));
}
}
MyAssemblyInstructionSet is the class name that you must change when implement this code snippet for you assembly instruction library.
How to compile
Compile is a process to transform strings code to machine code. In other words, is the process to get bytes from each intruction on the code source. All intructions you define inside your CPU or Assembly libraries would be converted to byte array or other things would you like, puting values from columns left or right used as entries.
If you like get bytes from a cpu instruction, you must invoke the GetInstruction passing the the line of code as a parameter.
//initialize provider.
IProvider<CpuInstructionType, byte> cpuCodeProvider = new Fjv.Z80.Provider();
IInstructionResolverProvider<byte> cpuCodeResolver = cpuCodeProvider.GetInstructionResolverProvider();
//remember the z80 LD instruction defined.
var line = "ld a, 0x01";
var instruction = cpuCodeResolver.GetInstruction(line);
SinglePassCompiler has an implementations that automatize this process to use with source files. Obviously you can create your own compiler.
You can see more on sample projects prepared to you on https://github.com/fpereiracalvo/fjv-xcpu.
Enjoy!
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0 | 421 | 3/20/2022 |