我正在尝试学习BNF并尝试组装一些Z80 ASM代码.由于我是两个领域的新手,我的问题是,我是否在正确的轨道上?我正在尝试将Z80 ASM的格式编写为EBNF,以便我可以从那里找出从源头创建机器代码的位置.目前我有以下内容:
Assignment = Identifier, ":" ;
Instruction = Opcode, [ Operand ], [ Operand ] ;
Operand = Identifier | Something* ;
Something* = "(" , Identifier, ")" ;
Identifier = Alpha, { Numeric | Alpha } ;
Opcode = Alpha, Alpha ;
Int = [ "-" ], Numeric, { Numeric } ;
Alpha = "A" | "B" | "C" | "D" | "E" | "F" |
"G" | "H" | "I" | "J" | "K" | "L" …
Run Code Online (Sandbox Code Playgroud) 我对编写汇编程序的概念非常陌生,即使在阅读了大量材料之后,我仍然难以绕过几个概念.
将源文件实际分解为令牌的过程是什么?我相信这个过程叫做lexing,我已经搜索了一些有意义的实际代码示例,但我找不到一个如此简单的代码示例非常受欢迎;)
在解析时,是否需要在树上向上或向下传递信息?我问的原因如下,采取:
LD BC,nn
一旦标记化(???),它需要变成以下的解析树
___ LD ___
| |
BC nn
Run Code Online (Sandbox Code Playgroud)
现在,当遍历此树时,它需要生成以下机器代码:
01 n n
Run Code Online (Sandbox Code Playgroud)
如果说明是:
LD DE,nn
Run Code Online (Sandbox Code Playgroud)
然后输出需要是:
11 n n
Run Code Online (Sandbox Code Playgroud)
这意味着它提出了问题,LD节点是否根据操作数返回不同的东西,或者它是返回某些东西的操作数?这是如何实现的?如果时间允许,更简单的代码示例将是非常好的.
我有一个奇怪的问题 - 我希望有人可以向我解释发生了什么,以及可能的解决方法.我在Java中实现了一个Z80核心,并试图通过在一个单独的线程中使用java.util.Timer对象来减慢它的速度.
基本设置是我有一个线程运行一个执行循环,每秒50次.在此执行循环中,执行许多循环,然后调用wait().外部Timer线程将每隔20ms调用Z80对象上的notifyAll(),模拟PAL Sega主系统时钟频率为3.54 MHz(ish).
我上面描述的方法在Windows 7(试过两台机器)上工作得很好,但我也试过两台Windows XP机器,而且在这两台机器上,Timer对象似乎睡过了大约50%左右.这意味着在Windows XP计算机上,一秒钟的仿真时间实际上大约需要1.5秒左右.
我尝试使用Thread.sleep()而不是Timer对象,但这具有完全相同的效果.我意识到大多数操作系统的时间粒度不会超过1毫秒,但我可以忍受999毫秒或1001毫秒而不是1000毫秒.我不能忍受的是1562ms - 我只是不明白为什么我的方法在较新版本的Windows上运行正常,但不是旧版本 - 我调查了中断期等等,但似乎没有已经开发出一种解决方法.
有谁能告诉我这个问题的原因和建议的解决方法?非常感谢.
更新:这是我为了显示相同问题而构建的较小应用的完整代码:
import java.util.Timer;
import java.util.TimerTask;
public class WorkThread extends Thread
{
private Timer timerThread;
private WakeUpTask timerTask;
public WorkThread()
{
timerThread = new Timer();
timerTask = new WakeUpTask(this);
}
public void run()
{
timerThread.schedule(timerTask, 0, 20);
while (true)
{
long startTime = System.nanoTime();
for (int i = 0; i < 50; i++)
{
int a = 1 + 1;
goToSleep();
}
long timeTaken = …
Run Code Online (Sandbox Code Playgroud) 我再次提出另一个无关紧要的Z80问题:-)我的仿真器内核当前的结构方式,每次从内存中取出操作码字节时,我都会递增内存刷新寄存器的低7位 - 这意味着对于多字节指令,例如作为开始DD或FD的那些,我将寄存器递增两次 - 或者在诸如RLC(IX + d)之类的指令的实例中递增三次(因为它布局为opcode1-opcode2-d-opcode3).
它是否正确?我不确定 - Z80手册有点不清楚,因为它说CPDR(一个双字节指令)将它增加两倍,但是"存储器刷新寄存器"部分仅表示它在每次取指令后递增.我注意到J80(我检查过的模拟器,因为我不确定)只在指令的第一个操作码字节后递增.
哪个是对的?我想这在任何情况下都不是非常重要,但是很高兴知道:-)非常感谢.
此致,菲尔波特
在Game Boy CPU Manual的第87页,声称该CP n
指令在没有借位时设置进位标志,这意味着A < n
.这似乎与自身发生冲突,因为进位标志设置时A > n
.
例如:如果A=0
和B=1
,则CP B
设置标志,如SUB A, B
0 - 1.这变为0 + 255 = 255并且未设置进位标志,即使如此A < B
.
我在其他Z80文档中也遇到过同样的问题,所以我不相信这是一个错字.
我是否误解了借用和SUB
工作的方式或是否有其他事情发生?是SUB
不是等于ADD
在标志方面补?
你好我正在为Game Boy写一个模拟器.
我用这个参考:Gameboy CPU(LR35902)指令集
该文件说明了操作码:
0xE2 LD (C),A
Run Code Online (Sandbox Code Playgroud)
和
0xF2 LD A,(C)
Run Code Online (Sandbox Code Playgroud)
长度为2.
该游戏男孩CPU手册说分别,这些说明:
将地址$ FF00 +寄存器C的值放入A.
和
将A放入地址$ FF00 +寄存器C.
我认为它的长度为1,因为操作码是自给自足的,它不需要另外的值来解释.如果它的长度为2,那么第二个字节的目的是什么?
我一直工作在一个游戏机模拟器,和我注意到,有一定的操作码存在是永远不会改变的任何值,如LD A, A
,LD B, B
等,并且还AND A
.第一个显然不会改变任何东西,因为它们将寄存器的值加载到相同的寄存器中,并且由于将AND
与A
寄存器进行比较,因此AND A
将始终返回A
.这些操作有什么用途,或者与NOP
每个循环后的操作基本相同?
我正在尝试运行以下代码:
HLDIVC:
LD B,16
D0: XOR A
ADD HL,HL
RLA
CP C
JR C, D1
INC L
SUB C
DJNZ D0
D1: RET
Run Code Online (Sandbox Code Playgroud)
这是原始代码的改编:(在这里找到)
HL_Div_C:
;Inputs:
; HL is the numerator
; C is the denominator
;Outputs:
; A is the remainder
; B is 0
; C is not changed
; DE is not changed
; HL is the quotient
;
ld b,16
xor a
add hl,hl
rla
cp c
jr c,$+4
inc l
sub c
djnz $-7 …
Run Code Online (Sandbox Code Playgroud) 我想使用Visual Studio Code为我的自制Z80计算机编写代码.我使用z88dk及其工具来编译C,组装Z80 asm文件等.我将在汇编程序和C中的其他部分编写部件.我还需要编辑.m4文件.如果这些文件类型具有语法着色会很好.
我对vscode是全新的,我已经看到了z80的一些语言,但我不知道如何从那里继续 - 或者如果这些甚至可以工作.
我不想写一个语言扩展,因为这看起来只是太多的工作 - 从我的理解.我也不是打字稿/ javascript开发者.
是否有关于如何设置它或任何其他有用资源的教程?
我从Z80的Zilog数据表中注意到,通过I/O(IN和OUT)指令组,各种寄存器的内容通常放在地址总线的前8位(取决于指令),低8位选择多达256个理论连接设备中的一个.
我的问题是用这8位高位做这个是什么意思?我知道有些机器在某种程度上使用它来降低解码复杂度,但它们是否真的用于任何事情?我想完全按照Z80的建议实现指令,但我没有看到实现这种行为的重点,因为它是非标准的.这种行为被描述为未记录,因此在"世嘉主系统"上,我会侥幸逃脱吗?非常感谢.
此致,菲尔波特