Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 71 additions & 50 deletions src/Avr8Sharp/Core/Utils/Assembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
var r = 0x2000 | DestRIndex(a) | SrcRIndex(b);
return ZeroPad (r);
} },
{ "ANDI", (a, b, _, _) => {
{ "ANDI", (a, b, byteLoc, labels) => {
if (ConstOrLabel(b, labels) == int.MinValue)
return new Func<Dictionary<string, int>, string> ((l) => OpTable?["ANDI"](a, b, byteLoc, l) as string ?? string.Empty);
var r = 0x7000 | (DestRIndex(a, 16, 31) & 0xf0);
var k = ConstValue(b);
r |= ((k & 0xf0) << 4) | (k & 0xf);
Expand Down Expand Up @@ -198,7 +200,9 @@
var r = 0x0400 | DestRIndex(a) | SrcRIndex(b);
return ZeroPad (r);
}},
{ "CPI", (a, b, _, _) => {
{ "CPI", (a, b, byteLoc, labels) => {
if (ConstOrLabel(b, labels) == int.MinValue)
return new Func<Dictionary<string, int>, string> ((l) => OpTable?["CPI"](a, b, byteLoc, l) as string ?? string.Empty);
var r = 0x3000 | (DestRIndex(a, 16, 31) & 0xf0);
var k = ConstValue(b);
r |= ((k & 0xf0) << 4) | (k & 0xf);
Expand Down Expand Up @@ -312,7 +316,9 @@
var r = DestRIndex(a) | StldYzQ(b);
return ZeroPad (r);
}},
{ "LDI", (a, b, _, _) => {
{ "LDI", (a, b, byteLoc, labels) => {
if (ConstOrLabel(b, labels) == int.MinValue)
return new Func<Dictionary<string, int>, string> ((l) => OpTable?["LDI"](a, b, byteLoc, l) as string ?? string.Empty);
var r = 0xe000 | (DestRIndex(a, 16, 31) & 0xf0);
var k = ConstValue(b);
r |= ((k & 0xf0) << 4) | (k & 0xf);
Expand Down Expand Up @@ -382,7 +388,9 @@
var r = 0x2800 | DestRIndex(a) | SrcRIndex(b);
return ZeroPad (r);
}},
{ "ORI", (a, b, _, _) => {
{ "ORI", (a, b, byteLoc, labels) => {
if (ConstOrLabel(b, labels) == int.MinValue)
return new Func<Dictionary<string, int>, string> ((l) => OpTable?["ORI"](a, b, byteLoc, l) as string ?? string.Empty);
var r = 0x6000 | (DestRIndex(a, 16, 31) & 0xf0);
var k = ConstValue(b);
r |= ((k & 0xf0) << 4) | (k & 0xf);
Expand Down Expand Up @@ -435,7 +443,9 @@
var r = 0x0800 | DestRIndex(a) | SrcRIndex(b);
return ZeroPad (r);
}},
{ "SBCI", (a, b, _, _) => {
{ "SBCI", (a, b, byteLoc, labels) => {
if (ConstOrLabel(b, labels) == int.MinValue)
return new Func<Dictionary<string, int>, string> ((l) => OpTable?["SBCI"](a, b, byteLoc, l) as string ?? string.Empty);
var r = 0x4000 | (DestRIndex(a, 16, 31) & 0xf0);
var k = ConstValue(b);
r |= ((k & 0xf0) << 4) | (k & 0xf);
Expand Down Expand Up @@ -540,7 +550,9 @@
var r = 0x1800 | DestRIndex(a) | SrcRIndex(b);
return ZeroPad (r);
}},
{ "SUBI", (a, b, _, _) => {
{ "SUBI", (a, b, byteLoc, labels) => {
if (ConstOrLabel(b, labels) == int.MinValue)
return new Func<Dictionary<string, int>, string> ((l) => OpTable?["SUBI"](a, b, byteLoc, l) as string ?? string.Empty);
var r = 0x5000 | (DestRIndex(a, 16, 31) & 0xf0);
var k = ConstValue(b);
r |= ((k & 0xf0) << 4) | (k & 0xf);
Expand Down Expand Up @@ -1132,7 +1144,9 @@
}

if (!OpTable.ContainsKey (instruction)) {
throw new Exception("Invalid instruction");
if (instruction == "__GCC_ISR")
throw new Exception("__gcc_isr is an avr-as ISR-prologue pseudo-op; recompile with -mno-gas-isr-prologues to get explicit push/pop prologues");
throw new Exception($"Invalid instruction: {instruction}");
}

// Apply replacements and symbol substitution on parameters
Expand Down Expand Up @@ -1241,7 +1255,8 @@
// -----------------------------------------------------------------------
private void ProcessSymbolDef(string args, int lineIdx, int byteOffset, bool isImmutable)
{
var parts = args.Split('=', 2);
// Accept both Atmel `NAME = VALUE` and GNU `NAME, VALUE` forms.
var parts = args.Contains('=') ? args.Split('=', 2) : args.Split(new[] { ',' }, 2);
if (parts.Length != 2) { _errors.Add($"Line {lineIdx + 1}: .equ/.set requires NAME = VALUE"); return; }
var name = parts[0].Trim();
var valStr = parts[1].Trim();
Expand Down Expand Up @@ -1290,68 +1305,74 @@
// Data-emission helpers
// -----------------------------------------------------------------------
private void EmitDataBytes(string args, int lineIdx, ref int byteOffset, string rawLine)
=> EmitData(args, lineIdx, ref byteOffset, rawLine, unit: 1);

private void EmitDataWords(string args, int lineIdx, ref int byteOffset, string rawLine)
=> EmitData(args, lineIdx, ref byteOffset, rawLine, unit: 2);

private void EmitDataDwords(string args, int lineIdx, ref int byteOffset, string rawLine)
=> EmitData(args, lineIdx, ref byteOffset, rawLine, unit: 4);

// Emit .byte/.word/.dword data. The byte count is always known up front, so
// byteOffset advances deterministically; if any element is a forward-referenced
// symbol expression, the actual bytes are produced by a deferred closure that
// re-evaluates in pass two (mirrors how instructions defer label resolution).
private void EmitData(string args, int lineIdx, ref int byteOffset, string rawLine, int unit)
{
var parts = SplitDirectiveArgs(args);
var bytes = new List<byte>();

// Byte count and whether any element needs deferral.
int count = 0;
bool deferred = false;
foreach (var part in parts)
{
var p = part.Trim();
if (p.StartsWith('"') || p.StartsWith('\''))
if (unit == 1 && (p.StartsWith('"') || p.StartsWith('\'')))
{
var s = p.Trim('"', '\'');
bytes.AddRange(System.Text.Encoding.ASCII.GetBytes(s));
count += p.Trim('"', '\'').Length;
continue;
}
var val = ExpressionEvaluator.TryEvaluate(p, _symbolTable, byteOffset);
if (val == null) { _errors.Add($"Line {lineIdx + 1}: Cannot evaluate .byte expression: {p}"); return; }
bytes.Add((byte)(val.Value & 0xFF));
}
if (bytes.Count > 0)
{
var lt = new LineTablePassOne { Text = rawLine.Trim(), Line = lineIdx + 1, BytesOffset = byteOffset, Bytes = bytes.ToArray() };
_lines.Add(lt);
byteOffset += bytes.Count;
count += unit;
if (ExpressionEvaluator.TryEvaluate(p, _symbolTable, byteOffset) == null)
deferred = true;
}
}
if (count == 0) return;

private void EmitDataWords(string args, int lineIdx, ref int byteOffset, string rawLine)
{
var parts = SplitDirectiveArgs(args);
var wordBytes = new List<byte>();
foreach (var part in parts)
int pc = byteOffset;
object bytes;
if (deferred)
{
var val = ExpressionEvaluator.TryEvaluate(part.Trim(), _symbolTable, byteOffset);
if (val == null) { _errors.Add($"Line {lineIdx + 1}: Cannot evaluate .word expression: {part.Trim()}"); return; }
wordBytes.Add((byte)(val.Value & 0xFF));
wordBytes.Add((byte)((val.Value >> 8) & 0xFF));
bytes = new Func<LabelTable, object>(_ => BuildDataBytes(parts, unit, pc));
}
if (wordBytes.Count > 0)
else
{
var lt = new LineTablePassOne { Text = rawLine.Trim(), Line = lineIdx + 1, BytesOffset = byteOffset, Bytes = wordBytes.ToArray() };
_lines.Add(lt);
byteOffset += wordBytes.Count;
try { bytes = BuildDataBytes(parts, unit, pc); }
catch (Exception ex) { _errors.Add($"Line {lineIdx + 1}: {ex.Message}"); return; }
}

_lines.Add(new LineTablePassOne { Text = rawLine.Trim(), Line = lineIdx + 1, BytesOffset = byteOffset, Bytes = bytes });
byteOffset += count;
}

private void EmitDataDwords(string args, int lineIdx, ref int byteOffset, string rawLine)
// Build the raw bytes for a .byte/.word/.dword directive. Throws if a symbol is
// still unresolved (caught by the caller / pass-two loop and reported as an error).
private byte[] BuildDataBytes(List<string> parts, int unit, int pc)
{
var parts = SplitDirectiveArgs(args);
var dwordBytes = new List<byte>();
var bytes = new List<byte>();
foreach (var part in parts)
{
var val = ExpressionEvaluator.TryEvaluate(part.Trim(), _symbolTable, byteOffset);
if (val == null) { _errors.Add($"Line {lineIdx + 1}: Cannot evaluate .dword expression: {part.Trim()}"); return; }
dwordBytes.Add((byte)(val.Value & 0xFF));
dwordBytes.Add((byte)((val.Value >> 8) & 0xFF));
dwordBytes.Add((byte)((val.Value >> 16) & 0xFF));
dwordBytes.Add((byte)((val.Value >> 24) & 0xFF));
}
if (dwordBytes.Count > 0)
{
var lt = new LineTablePassOne { Text = rawLine.Trim(), Line = lineIdx + 1, BytesOffset = byteOffset, Bytes = dwordBytes.ToArray() };
_lines.Add(lt);
byteOffset += dwordBytes.Count;
var p = part.Trim();
if (unit == 1 && (p.StartsWith('"') || p.StartsWith('\'')))
{
bytes.AddRange(System.Text.Encoding.ASCII.GetBytes(p.Trim('"', '\'')));
continue;
}
var val = ExpressionEvaluator.TryEvaluate(p, _currentSymbolTable ?? _symbolTable, pc)
?? throw new Exception($"Cannot evaluate data expression: {p}");
for (int i = 0; i < unit; i++)
bytes.Add((byte)((val >> (8 * i)) & 0xFF));
}
return bytes.ToArray();
}

private void EmitDataAscii(string args, int lineIdx, ref int byteOffset, string rawLine, bool nullTerminated)
Expand Down Expand Up @@ -1773,14 +1794,14 @@
public class LineTablePassOne
{
public int Line { get; set; }
public object Bytes { get; set; }

Check warning on line 1797 in src/Avr8Sharp/Core/Utils/Assembler.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Non-nullable property 'Bytes' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 1797 in src/Avr8Sharp/Core/Utils/Assembler.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Non-nullable property 'Bytes' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string Text { get; set; }

Check warning on line 1798 in src/Avr8Sharp/Core/Utils/Assembler.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Non-nullable property 'Text' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 1798 in src/Avr8Sharp/Core/Utils/Assembler.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Non-nullable property 'Text' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public int BytesOffset { get; set; }
}

public class LineTable : LineTablePassOne
{
public new string Bytes { get; set; }

Check warning on line 1804 in src/Avr8Sharp/Core/Utils/Assembler.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Non-nullable property 'Bytes' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 1804 in src/Avr8Sharp/Core/Utils/Assembler.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Non-nullable property 'Bytes' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
}

/// <summary>
Expand Down
4 changes: 4 additions & 0 deletions src/Avr8Sharp/Core/Utils/ExpressionEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ public static int Evaluate(string expr, SymbolTable symbols, int currentPc = 0)
"LWRD" => arg.Value & 0xFFFF,
"HWRD" => (arg.Value >> 16) & 0xFFFF,
"PM" => arg.Value >> 1,
// gs() yields the word address of a symbol (for function pointers /
// far jumps). On <=128KW parts this equals pm(); the >128K stub is
// not needed for the simulated devices.
"GS" => arg.Value >> 1,
_ => throw new Exception($"Unknown function: {name}")
};
}
Expand Down
63 changes: 63 additions & 0 deletions tests/Avr8Sharp.Tests/AssemblerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,69 @@ public void Deferred4ByteAsLastLine ()
Assert.That (Bytes ("00910001"), Is.EqualTo (lds.Assemble ("lds r16, x\n.data\nx: .byte 1")));
}

[Test(Description = "Immediate instructions defer forward-referenced symbol operands (avr-gcc ldi lo8(sym))")]
public void Immediate_ForwardSymbol ()
{
// buf resolves to 0x100: lo8 = 0x00, hi8 = 0x01
var lo = new AvrAssembler ();
Assert.That (Bytes ("80e0"), Is.EqualTo (lo.Assemble ("ldi r24, lo8(buf)\n.data\nbuf: .byte 1")));
var hi = new AvrAssembler ();
Assert.That (Bytes ("91e0"), Is.EqualTo (hi.Assemble ("ldi r25, hi8(buf)\n.data\nbuf: .byte 1")));
// subi/sbci/ori/andi/cpi take the same path
var subi = new AvrAssembler ();
subi.Assemble ("subi r24, lo8(-(buf))\n.data\nbuf: .byte 1");
Assert.That (subi.Errors, Is.Empty);
}

[Test(Description = "gs() yields the word address of a symbol (function pointers), matching avr-as")]
public void GsFunction ()
{
var assembler = new AvrAssembler ();
// f at byte 8 (word 4): lo8(gs(f)) = 0x04, hi8(gs(f)) = 0x00
var result = assembler.Assemble ("ldi r24, lo8(gs(f))\nldi r25, hi8(gs(f))\nnop\nnop\nf: ret");

Assert.That (assembler.Errors, Is.Empty);
Assert.That (Bytes ("84e0" + "90e0" + "0000" + "0000" + "0895"), Is.EqualTo (result));
}

[Test(Description = ".set / .equ accept the GNU comma form (NAME, VALUE)")]
public void SymbolDef_CommaForm ()
{
var s = new AvrAssembler ();
Assert.That (Bytes ("0ae2"), Is.EqualTo (s.Assemble (".set foo, 0x2a\nldi r16, foo"))); // ldi r16,0x2a
var e = new AvrAssembler ();
Assert.That (Bytes ("05e5"), Is.EqualTo (e.Assemble (".equ bar, 0x55\nldi r16, bar"))); // ldi r16,0x55
// the Atmel '=' form still works
var eq = new AvrAssembler ();
Assert.That (Bytes ("00e1"), Is.EqualTo (eq.Assemble (".equ baz = 0x10\nldi r16, baz")));
}

[Test(Description = ".byte/.word with a forward-referenced symbol expression defers to pass two")]
public void DataDirective_ForwardSymbol ()
{
// x at byte 2: lo8(x)=0x02, hi8(x)=0x00; then a NOP
var b = new AvrAssembler ();
var rb = b.Assemble (".byte lo8(x), hi8(x)\nx: nop");
Assert.That (b.Errors, Is.Empty);
Assert.That (Bytes ("0200" + "0000"), Is.EqualTo (rb));

// jump-table style: .word of forward labels (a=byte4, b=byte6) then two NOPs
var w = new AvrAssembler ();
var rw = w.Assemble (".word a, b\na: nop\nb: nop");
Assert.That (w.Errors, Is.Empty);
Assert.That (Bytes ("0400" + "0600" + "0000" + "0000"), Is.EqualTo (rw));
}

[Test(Description = "__gcc_isr gives an actionable error pointing to -mno-gas-isr-prologues")]
public void GccIsr_ActionableError ()
{
var assembler = new AvrAssembler ();
assembler.Assemble ("__vector_1:\n__gcc_isr 1\nreti");

Assert.That (assembler.Errors, Is.Not.Empty);
Assert.That (assembler.Errors[0], Does.Contain ("-mno-gas-isr-prologues"));
}

// -----------------------------------------------------------------------
// avr-gcc / avr-as front-end compatibility
// -----------------------------------------------------------------------
Expand Down
Loading